From bdad7e32ee5ed58786ba46b0fddd345256885940 Mon Sep 17 00:00:00 2001 From: SagorIslamOfficial Date: Tue, 9 Sep 2025 18:14:22 +0600 Subject: [PATCH 01/17] feat: add initial timeline element --- timeline/config.yml | 274 ++++++++++++++++++++++++++++++++++ timeline/element.php | 15 ++ timeline/element.svg | 6 + timeline/partials/html.twig | 146 ++++++++++++++++++ timeline/partials/script.twig | 78 ++++++++++ timeline/partials/style.twig | 239 +++++++++++++++++++++++++++++ 6 files changed, 758 insertions(+) create mode 100644 timeline/config.yml create mode 100644 timeline/element.php create mode 100644 timeline/element.svg create mode 100644 timeline/partials/html.twig create mode 100644 timeline/partials/script.twig create mode 100644 timeline/partials/style.twig diff --git a/timeline/config.yml b/timeline/config.yml new file mode 100644 index 0000000..48f4095 --- /dev/null +++ b/timeline/config.yml @@ -0,0 +1,274 @@ +name: Timeline +slug: timeline +groups: general +helpId: timeline-element +form: + general: + - name: timeline_items + label: Timeline Items + type: group-repeater + schema: + - name: title + label: Title + type: text + value: Timeline Item Title + + - name: date + label: Date/Time + type: text + value: January 2024 + help: Enter the date or time for this timeline item + + - name: description + label: Description + type: editor + value: Add your timeline item description here. + + - name: image + label: Image + type: media + filters: image + showstyle: true + + - name: icon + label: Icon + type: media + filters: icon + help: Choose an icon for this timeline item + + - name: link + label: Link + type: link + help: Optional link for the timeline item + + - name: active + label: Active State + type: switch + value: false + help: Mark this item as currently active/highlighted + + - name: layout_settings + label: Layout Settings + type: fields-group + status: open + schema: + - name: layout + label: Layout Type + type: select + value: vertical + options: + vertical: Vertical + horizontal: Horizontal + + - name: alignment + label: Timeline Alignment + type: choose + value: center + responsive: false + options: + left: + label: Left + icon: qxuicon-align-left + center: + label: Center + icon: qxuicon-align-center + right: + label: Right + icon: qxuicon-align-right + + - name: show_connector + label: Show Timeline Connector + type: switch + value: true + help: Display the connecting line between timeline items + + - name: connector_style + label: Connector Style + type: select + value: solid + depends: + show_connector: true + options: + solid: Solid + dashed: Dashed + dotted: Dotted + + styles: + - name: timeline_appearance + label: Timeline Appearance + type: fields-group + status: open + schema: + - name: timeline_bg + label: Timeline Background + type: background + popover: true + + - name: connector_color + label: Connector Color + type: color + value: '#e5e5e5' + + - name: connector_width + label: Connector Width + type: slider + min: 1 + max: 10 + suffix: px + value: 2 + + - name: item_spacing + label: Item Spacing + type: slider + max: 100 + suffix: px + value: 20 + responsive: true + + - name: item_styles + label: Timeline Item Styles + type: fields-group + schema: + - name: item_bg + label: Item Background + type: background + popover: true + + - name: item_border + label: Item Border + type: border + popover: true + + - name: item_padding + label: Item Padding + type: dimensions + units: px + defaultUnit: px + value: + desktop: + top: 20 + bottom: 20 + left: 20 + right: 20 + + - name: item_border_radius + label: Border Radius + type: dimensions + units: px + defaultUnit: px + + - name: item_shadow + label: Box Shadow + type: box-shadow + popover: true + + - name: icon_styles + label: Icon Styles + type: fields-group + schema: + - name: icon_bg + label: Icon Background + type: background + popover: true + + - name: icon_padding + label: Icon Padding + type: slider + min: 0 + max: 50 + suffix: px + value: 10 + + - name: icon_border_radius + label: Icon Border Radius + type: slider + min: 0 + max: 100 + suffix: % + value: 50 + + - name: content_styles + label: Content Styles + type: fields-group + schema: + - name: title_color + label: Title Color + type: color + value: '#333333' + + - name: title_font + label: Title Typography + type: typography + popover: true + + - name: date_color + label: Date Color + type: color + value: '#666666' + + - name: date_font + label: Date Typography + type: typography + popover: true + + - name: description_color + label: Description Color + type: color + value: '#666666' + + - name: description_font + label: Description Typography + type: typography + popover: true + + - name: hover_effects + label: Hover Effects + type: fields-group + schema: + - name: enable_hover + label: Enable Hover Effects + type: switch + value: true + + - name: hover_item_bg + label: Hover Item Background + type: background + popover: true + depends: + enable_hover: true + + - name: hover_title_color + label: Hover Title Color + type: color + depends: + enable_hover: true + + - name: hover_animation + label: Hover Animation + type: select + value: none + depends: + enable_hover: true + options: + none: None + fade: Fade + slide: Slide + scale: Scale + + - name: responsive_settings + label: Responsive Settings + type: fields-group + schema: + - name: mobile_layout + label: Mobile Layout + type: select + value: vertical + options: + vertical: Vertical + horizontal: Horizontal + + - name: hide_on_mobile + label: Hide Elements on Mobile + type: switch + value: false + help: Hide certain elements on mobile devices to save space diff --git a/timeline/element.php b/timeline/element.php new file mode 100644 index 0000000..1c9037b --- /dev/null +++ b/timeline/element.php @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/timeline/element.svg b/timeline/element.svg new file mode 100644 index 0000000..e00f026 --- /dev/null +++ b/timeline/element.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/timeline/partials/html.twig b/timeline/partials/html.twig new file mode 100644 index 0000000..451614f --- /dev/null +++ b/timeline/partials/html.twig @@ -0,0 +1,146 @@ +{% set id = advanced.identifier.id %} +{% set class = advanced.identifier.class %} +{% set timelineItems = general.timeline_items %} +{% set layout = general.layout_settings.layout %} +{% set alignment = general.layout_settings.alignment %} +{% set showConnector = general.layout_settings.show_connector %} +{% set connectorStyle = general.layout_settings.connector_style %} + +{% set classes = classNames('qx-element qx-element-timeline-v2', + 'qx-timeline-' ~ layout, + 'qx-timeline-align-' ~ alignment, + visibilityClass(visibility), + class +) %} + +{% set animation = advanced.animation_fields_group.animation %} +{% set animationRepeat = advanced.animation_fields_group.animation_repeat %} +{% set animationDelay = advanced.animation_fields_group.animation_delay %} +{% set background = advanced.background_fields_group.background %} + +{% embed "animation.twig" with { + "id" : id, + "classes" : classes, + "animation" : animation, + "animationRepeat" : animationRepeat, + "animationDelay" : animationDelay, + "background" : background +} %} + {% block element %} + {% if timelineItems %} +
+ {% if layout == 'vertical' %} +
+ {% for item in timelineItems %} +
+ {% if showConnector %} +
+ {% endif %} +
+ {% if item.icon.source %} + {% if item.icon.type == "svg" %} + {{ icon(item.icon.source) }} + {% else %} + Timeline icon + {% endif %} + {% else %} +
+ {% endif %} +
+ + {% if item.icon.type == "svg" %} + {{ addIconStyle('#' ~ id ~ ' .qx-timeline-marker', item.icon) }} + {% endif %} + +
+ {% if item.date %} +
{{ item.date }}
+ {% endif %} + + {% if item.title %} +

+ {% if item.link.url %} + {{ item.title | link(item.link) }} + {% else %} + {{ item.title }} + {% endif %} +

+ {% endif %} + + {% if item.image.source %} +
+ {{ image(item.image.source, item.title, '', '', item.image) }} +
+ {% endif %} + + {% if item.description %} +
+ {{ item.description|raw }} +
+ {% endif %} +
+
+ {% endfor %} +
+ {% else %} +
+ {% for item in timelineItems %} +
+ {% if showConnector %} +
+ {% endif %} +
+ {% if item.icon.source %} + {% if item.icon.type == "svg" %} + {{ icon(item.icon.source) }} + {% else %} + Timeline icon + {% endif %} + {% else %} +
+ {% endif %} +
+ + {% if item.icon.type == "svg" %} + {{ addIconStyle('#' ~ id ~ ' .qx-timeline-marker', item.icon) }} + {% endif %} + +
+ {% if item.date %} +
{{ item.date }}
+ {% endif %} + + {% if item.title %} +

+ {% if item.link.url %} + {{ item.title | link(item.link) }} + {% else %} + {{ item.title }} + {% endif %} +

+ {% endif %} + + {% if item.image.source %} +
+ {{ image(item.image.source, item.title, '', '', item.image) }} +
+ {% endif %} + + {% if item.description %} +
+ {{ item.description|raw }} +
+ {% endif %} +
+
+ {% endfor %} +
+ {% endif %} +
+ {% else %} +
+

Add timeline items to get started

+
+ {% endif %} + {% endblock %} +{% endembed %} diff --git a/timeline/partials/script.twig b/timeline/partials/script.twig new file mode 100644 index 0000000..b9350f8 --- /dev/null +++ b/timeline/partials/script.twig @@ -0,0 +1,78 @@ +{% set id = advanced.identifier.id %} +{% set enableHover = styles.hover_effects.enable_hover %} +{% set hoverAnimation = styles.hover_effects.hover_animation %} + +{% if mode != 'builder' %} +jQuery(document).ready(function($) { + var timelineId = "{{ id }}"; + var $timeline = $('#' + timelineId); + + function initTimelineAnimations() { + if (typeof IntersectionObserver !== 'undefined') { + var observer = new IntersectionObserver(function(entries) { + entries.forEach(function(entry) { + if (entry.isIntersecting) { + var $item = $(entry.target); + $item.addClass('qx-timeline-item-visible'); + + {% if enableHover and hoverAnimation != 'none' %} + setTimeout(function() { + $item.addClass('qx-timeline-item-animated'); + }, 200); + {% endif %} + } + }); + }, { + threshold: 0.1, + rootMargin: '0px 0px -50px 0px' + }); + + $timeline.find('.qx-timeline-item').each(function() { + observer.observe(this); + }); + } else { + $timeline.find('.qx-timeline-item').addClass('qx-timeline-item-visible'); + } + } + + function handleHashNavigation() { + if (window.location.hash) { + var hash = window.location.hash; + var $target = $timeline.find(hash); + + if ($target.length) { + setTimeout(function() { + $('html, body').animate({ + scrollTop: $target.offset().top - 100 + }, 800); + }, 500); + } + } + } + + $timeline.find('.qx-timeline-item a').on('click', function(e) { + var $link = $(this); + var href = $link.attr('href'); + + if (href && href.startsWith('#')) { + e.preventDefault(); + var $target = $(href); + + if ($target.length) { + $('html, body').animate({ + scrollTop: $target.offset().top - 100 + }, 800, function() { + history.pushState(null, null, href); + }); + } + } + }); + + initTimelineAnimations(); + handleHashNavigation(); + + $(window).on('resize', function() { + setTimeout(initTimelineAnimations, 300); + }); +}); +{% endif %} diff --git a/timeline/partials/style.twig b/timeline/partials/style.twig new file mode 100644 index 0000000..14067a4 --- /dev/null +++ b/timeline/partials/style.twig @@ -0,0 +1,239 @@ +{% include 'global.twig' %} + +{% set id = '#' ~ advanced.identifier.id %} +{% set timelineBg = styles.timeline_appearance.timeline_bg %} +{% set connectorColor = styles.timeline_appearance.connector_color %} + +{# Extract slider values with responsive support #} +{% set connectorWidth = (styles.timeline_appearance.connector_width.desktop ?? styles.timeline_appearance.connector_width.value ?? styles.timeline_appearance.connector_width ?: 2) %} +{% set itemSpacing = styles.timeline_appearance.item_spacing %} + +{% set itemBg = styles.item_styles.item_bg %} +{% set itemBorder = styles.item_styles.item_border %} +{% set itemPadding = styles.item_styles.item_padding %} +{% set itemBorderRadius = styles.item_styles.item_border_radius %} +{% set itemShadow = styles.item_styles.item_shadow %} + +{% set iconBg = styles.icon_styles.icon_bg %} + +{# Extract slider values with responsive support #} +{% set iconPadding = (styles.icon_styles.icon_padding.desktop ?? styles.icon_styles.icon_padding.value ?? styles.icon_styles.icon_padding ?: 10) %} +{% set iconBorderRadius = (styles.icon_styles.icon_border_radius.desktop ?? styles.icon_styles.icon_border_radius.value ?? styles.icon_styles.icon_border_radius ?: 50) %} + +{% set titleColor = styles.content_styles.title_color %} +{% set titleFont = styles.content_styles.title_font %} +{% set dateColor = styles.content_styles.date_color %} +{% set dateFont = styles.content_styles.date_font %} +{% set descriptionColor = styles.content_styles.description_color %} +{% set descriptionFont = styles.content_styles.description_font %} + +{% set enableHover = styles.hover_effects.enable_hover %} +{% set hoverItemBg = styles.hover_effects.hover_item_bg %} +{% set hoverTitleColor = styles.hover_effects.hover_title_color %} +{% set hoverAnimation = styles.hover_effects.hover_animation %} + +{# Timeline Wrapper #} +{{ style.css(id ~ ' .qx-timeline-wrapper', 'position', 'relative') }} +{{ style.background(id ~ ' .qx-timeline-wrapper', timelineBg) }} + +{# Timeline Base Styles #} +{{ style.css(id ~ ' .qx-timeline', 'position', 'relative') }} +{{ style.css(id ~ ' .qx-timeline', 'list-style', 'none') }} +{{ style.css(id ~ ' .qx-timeline', 'margin', '0') }} +{{ style.css(id ~ ' .qx-timeline', 'padding', '0') }} + +{# Timeline Connector #} +{{ style.css(id ~ ' .qx-timeline-connector', 'position', 'absolute') }} +{{ style.css(id ~ ' .qx-timeline-connector', 'background-color', connectorColor) }} +{{ style.css(id ~ ' .qx-timeline-connector', 'width', connectorWidth ~ 'px') }} +{{ style.css(id ~ ' .qx-timeline-connector', 'border-radius', connectorWidth ~ 'px') }} + +{# Connector Styles #} +{{ style.css(id ~ ' .qx-timeline-connector-dashed', 'border-style', 'dashed') }} +{{ style.css(id ~ ' .qx-timeline-connector-dashed', 'border-width', '0 ' ~ connectorWidth ~ 'px') }} +{{ style.css(id ~ ' .qx-timeline-connector-dashed', 'background-color', 'transparent') }} +{{ style.css(id ~ ' .qx-timeline-connector-dashed', 'border-color', connectorColor) }} + +{{ style.css(id ~ ' .qx-timeline-connector-dotted', 'border-style', 'dotted') }} +{{ style.css(id ~ ' .qx-timeline-connector-dotted', 'border-width', '0 ' ~ connectorWidth ~ 'px') }} +{{ style.css(id ~ ' .qx-timeline-connector-dotted', 'background-color', 'transparent') }} +{{ style.css(id ~ ' .qx-timeline-connector-dotted', 'border-color', connectorColor) }} + +{# Horizontal Connector Styles #} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item .qx-timeline-connector-dashed', 'border-width', connectorWidth ~ 'px 0') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item .qx-timeline-connector-dotted', 'border-width', connectorWidth ~ 'px 0') }} + +{# Vertical Timeline #} +{{ style.css(id ~ ' .qx-timeline-vertical', 'padding-left', '60px') }} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item .qx-timeline-connector', 'left', '-50px') }} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item .qx-timeline-connector', 'height', 'calc(100%)') }} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item .qx-timeline-connector', 'top', '20px') }} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item .qx-timeline-connector', 'width', connectorWidth ~ 'px') }} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item:last-child .qx-timeline-connector', 'height', 'calc(100% - 20px)') }} + +{# Horizontal Timeline #} +{{ style.css(id ~ ' .qx-timeline-horizontal', 'display', 'flex') }} +{{ style.css(id ~ ' .qx-timeline-horizontal', 'justify-content', 'space-between') }} +{{ style.css(id ~ ' .qx-timeline-horizontal', 'align-items', 'flex-start') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item .qx-timeline-connector', 'top', '0px') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item .qx-timeline-connector', 'left', '50%') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item .qx-timeline-connector', 'transform', 'translateX(-50%)') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item .qx-timeline-connector', 'width', '100%') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item .qx-timeline-connector', 'height', connectorWidth ~ 'px') }} + +{# Timeline Items #} +{{ style.css(id ~ ' .qx-timeline-item', 'position', 'relative') }} +{{ style.css(id ~ ' .qx-timeline-item', 'margin-bottom', '0') }} +{{ style.responsiveCss(id ~ ' .qx-timeline-item', itemSpacing, 'margin-bottom', 'px') }} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item:last-child', 'margin-bottom', '0') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'flex', '1') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item:not(:last-child)', 'margin-right', '20px') }} + +{# Timeline Marker #} +{{ style.css(id ~ ' .qx-timeline-marker', 'position', 'absolute') }} +{{ style.css(id ~ ' .qx-timeline-marker', 'border-radius', iconBorderRadius ~ '%') }} +{{ style.css(id ~ ' .qx-timeline-marker', 'padding', iconPadding ~ 'px') }} +{{ style.css(id ~ ' .qx-timeline-marker', 'display', 'flex') }} +{{ style.css(id ~ ' .qx-timeline-marker', 'align-items', 'center') }} +{{ style.css(id ~ ' .qx-timeline-marker', 'justify-content', 'center') }} +{{ style.css(id ~ ' .qx-timeline-marker', 'z-index', '2') }} + +{# Vertical Marker Position #} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'left', '-60px') }} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'top', '0') }} + +{# Horizontal Marker Position #} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'top', '-60px') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'left', '50%') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'transform', 'translateX(-50%)') }} + +{# Timeline Dot (default marker) #} +{{ style.css(id ~ ' .qx-timeline-dot', 'width', '12px') }} +{{ style.css(id ~ ' .qx-timeline-dot', 'height', '12px') }} +{{ style.css(id ~ ' .qx-timeline-dot', 'border-radius', '50%') }} +{{ style.css(id ~ ' .qx-timeline-dot', 'background-color', connectorColor) }} + +{# Icon Styling #} +{{ style.background(id ~ ' .qx-timeline-marker', iconBg) }} + +{# Timeline Content #} +{{ style.css(id ~ ' .qx-timeline-content', 'position', 'relative') }} +{{ style.background(id ~ ' .qx-timeline-content', itemBg) }} +{{ style.border(id ~ ' .qx-timeline-content', itemBorder) }} +{{ style.padding(id ~ ' .qx-timeline-content', itemPadding) }} +{{ style.borderRadius(id ~ ' .qx-timeline-content', itemBorderRadius) }} +{{ style.boxShadow(id ~ ' .qx-timeline-content', itemShadow) }} + +{# Timeline Date #} +{{ style.css(id ~ ' .qx-timeline-date', 'color', dateColor) }} +{{ style.css(id ~ ' .qx-timeline-date', 'margin-bottom', '8px') }} +{{ style.css(id ~ ' .qx-timeline-date', 'font-size', '14px') }} +{{ style.css(id ~ ' .qx-timeline-date', 'font-weight', '500') }} +{{ style.typography(id ~ ' .qx-timeline-date', dateFont) }} + +{# Timeline Title #} +{{ style.css(id ~ ' .qx-timeline-title', 'color', titleColor) }} +{{ style.css(id ~ ' .qx-timeline-title', 'margin-bottom', '12px') }} +{{ style.css(id ~ ' .qx-timeline-title', 'margin-top', '0') }} +{{ style.typography(id ~ ' .qx-timeline-title', titleFont) }} +{{ style.css(id ~ ' .qx-timeline-title a', 'color', 'inherit') }} +{{ style.css(id ~ ' .qx-timeline-title a:hover', 'color', 'inherit') }} +{{ style.css(id ~ ' .qx-timeline-title a', 'text-decoration', 'none') }} + +{# Timeline Image #} +{{ style.css(id ~ ' .qx-timeline-image', 'margin-bottom', '15px') }} +{{ style.css(id ~ ' .qx-timeline-image img', 'max-width', '100%') }} +{{ style.css(id ~ ' .qx-timeline-image img', 'height', 'auto') }} +{{ style.css(id ~ ' .qx-timeline-image img', 'border-radius', '4px') }} + +{# Timeline Description #} +{{ style.css(id ~ ' .qx-timeline-description', 'color', descriptionColor) }} +{{ style.css(id ~ ' .qx-timeline-description', 'margin-bottom', '0') }} +{{ style.typography(id ~ ' .qx-timeline-description', descriptionFont) }} +{{ style.css(id ~ ' .qx-timeline-description p:last-child', 'margin-bottom', '0') }} + +{# Active Item Styling #} +{{ style.css(id ~ ' .qx-timeline-item-active .qx-timeline-marker', 'transform', 'scale(1.2)') }} +{{ style.css(id ~ ' .qx-timeline-item-active .qx-timeline-dot', 'background-color', titleColor) }} + +{# Hover Effects #} +{% if enableHover %} + {{ style.css(id ~ ' .qx-timeline-item', 'transition', 'all 0.3s ease') }} + + {% if hoverAnimation == 'fade' %} + {{ style.css(id ~ ' .qx-timeline-item:hover', 'opacity', '0.8') }} + {% elseif hoverAnimation == 'scale' %} + {{ style.css(id ~ ' .qx-timeline-item:hover', 'transform', 'scale(1.05)') }} + {% elseif hoverAnimation == 'slide' %} + {{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item:hover', 'transform', 'translateX(10px)') }} + {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item:hover', 'transform', 'translateY(-10px)') }} + {% endif %} + + {% if hoverItemBg %} + {{ style.background(id ~ ' .qx-timeline-item:hover .qx-timeline-content', hoverItemBg) }} + {% endif %} + + {% if hoverTitleColor %} + {{ style.css(id ~ ' .qx-timeline-item:hover .qx-timeline-title', 'color', hoverTitleColor) }} + {% endif %} +{% endif %} + +{# Responsive Design #} +{{ style.phone(id ~ ' .qx-timeline-vertical', 'padding-left: 40px') }} +{{ style.phone(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'left: -40px') }} +{{ style.phone(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'width: 30px') }} +{{ style.phone(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'height: 30px') }} +{{ style.phone(id ~ ' .qx-timeline-vertical .qx-timeline-connector', 'left: 10px') }} + +{{ style.phone(id ~ ' .qx-timeline-horizontal', 'flex-direction: column') }} +{{ style.phone(id ~ ' .qx-timeline-horizontal', 'padding-top: 40px') }} +{{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'top: -40px') }} +{{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'left: -40px') }} +{{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'transform: none') }} +{{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'display: none') }} +{{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'margin-right: 0') }} + +{# Placeholder Styling #} +{{ style.css(id ~ ' .qx-timeline-placeholder', 'text-align', 'center') }} +{{ style.css(id ~ ' .qx-timeline-placeholder', 'padding', '40px') }} +{{ style.css(id ~ ' .qx-timeline-placeholder', 'color', '#999') }} +{{ style.css(id ~ ' .qx-timeline-placeholder p', 'margin', '0') }} + +{# JavaScript Animation Classes #} +{{ style.css(id ~ ' .qx-timeline-item', 'opacity', '0') }} +{{ style.css(id ~ ' .qx-timeline-item', 'transform', 'translateY(30px)') }} +{{ style.css(id ~ ' .qx-timeline-item', 'transition', 'opacity 0.6s ease, transform 0.6s ease') }} + +{{ style.css(id ~ ' .qx-timeline-item-visible', 'opacity', '1') }} +{{ style.css(id ~ ' .qx-timeline-item-visible', 'transform', 'translateY(0)') }} + +{% if enableHover and hoverAnimation != 'none' %} + {{ style.css(id ~ ' .qx-timeline-item-animated', 'animation-duration', '0.5s') }} + {{ style.css(id ~ ' .qx-timeline-item-animated', 'animation-fill-mode', 'both') }} + + {% if hoverAnimation == 'fade' %} + {{ style.css(id ~ ' .qx-timeline-item-animated', 'animation-name', 'qx-timeline-fade-in') }} + {% elseif hoverAnimation == 'scale' %} + {{ style.css(id ~ ' .qx-timeline-item-animated', 'animation-name', 'qx-timeline-scale-in') }} + {% elseif hoverAnimation == 'slide' %} + {{ style.css(id ~ ' .qx-timeline-item-animated', 'animation-name', 'qx-timeline-slide-in') }} + {% endif %} +{% endif %} + +{# Animation Keyframes #} +@keyframes qx-timeline-fade-in { + 0% { opacity: 0; } + 100% { opacity: 1; } +} + +@keyframes qx-timeline-scale-in { + 0% { transform: scale(0.8); opacity: 0; } + 100% { transform: scale(1); opacity: 1; } +} + +@keyframes qx-timeline-slide-in { + 0% { transform: translateX(-30px); opacity: 0; } + 100% { transform: translateX(0); opacity: 1; } +} + +{{ style.load(id) }} From d1d5d1c8b46a0252904dcd6c3030ebcdbd3a2243 Mon Sep 17 00:00:00 2001 From: SagorIslamOfficial Date: Wed, 10 Sep 2025 14:20:52 +0600 Subject: [PATCH 02/17] refactor: remove item border radius setting and adjust connector styles --- timeline/config.yml | 6 ------ timeline/partials/style.twig | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/timeline/config.yml b/timeline/config.yml index 48f4095..5d0b974 100644 --- a/timeline/config.yml +++ b/timeline/config.yml @@ -151,12 +151,6 @@ form: left: 20 right: 20 - - name: item_border_radius - label: Border Radius - type: dimensions - units: px - defaultUnit: px - - name: item_shadow label: Box Shadow type: box-shadow diff --git a/timeline/partials/style.twig b/timeline/partials/style.twig index 14067a4..420ce64 100644 --- a/timeline/partials/style.twig +++ b/timeline/partials/style.twig @@ -65,7 +65,7 @@ {# Vertical Timeline #} {{ style.css(id ~ ' .qx-timeline-vertical', 'padding-left', '60px') }} -{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item .qx-timeline-connector', 'left', '-50px') }} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item .qx-timeline-connector', 'left', '-46px') }} {{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item .qx-timeline-connector', 'height', 'calc(100%)') }} {{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item .qx-timeline-connector', 'top', '20px') }} {{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item .qx-timeline-connector', 'width', connectorWidth ~ 'px') }} @@ -154,7 +154,7 @@ {# Active Item Styling #} {{ style.css(id ~ ' .qx-timeline-item-active .qx-timeline-marker', 'transform', 'scale(1.2)') }} -{{ style.css(id ~ ' .qx-timeline-item-active .qx-timeline-dot', 'background-color', titleColor) }} +{{ style.css(id ~ ' .qx-timeline-item-active .qx-timeline-dot', 'background-color', connectorColor) }} {# Hover Effects #} {% if enableHover %} From 2652dc666027dc444cf3c1808ddc81a1d2257807 Mon Sep 17 00:00:00 2001 From: SagorIslamOfficial Date: Wed, 10 Sep 2025 15:28:56 +0600 Subject: [PATCH 03/17] feat: add alignment styles --- timeline/partials/style.twig | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/timeline/partials/style.twig b/timeline/partials/style.twig index 420ce64..85e1109 100644 --- a/timeline/partials/style.twig +++ b/timeline/partials/style.twig @@ -42,6 +42,23 @@ {{ style.css(id ~ ' .qx-timeline', 'margin', '0') }} {{ style.css(id ~ ' .qx-timeline', 'padding', '0') }} +{# Timeline Alignment Styles #} +{{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-left .qx-timeline-content', 'text-align', 'left') }} +{{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-center .qx-timeline-content', 'text-align', 'center') }} +{{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-right .qx-timeline-content', 'text-align', 'right') }} + +{# Updated: Center/left/right align images using flexbox #} +{{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-left .qx-timeline-image', 'display', 'flex') }} +{{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-left .qx-timeline-image', 'justify-content', 'flex-start') }} +{{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-center .qx-timeline-image', 'display', 'flex') }} +{{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-center .qx-timeline-image', 'justify-content', 'center') }} +{{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-right .qx-timeline-image', 'display', 'flex') }} +{{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-right .qx-timeline-image', 'justify-content', 'flex-end') }} + +{{ style.css(id ~ '.qx-timeline-align-left .qx-timeline-horizontal', 'justify-content', 'flex-start') }} +{{ style.css(id ~ '.qx-timeline-align-center .qx-timeline-horizontal', 'justify-content', 'center') }} +{{ style.css(id ~ '.qx-timeline-align-right .qx-timeline-horizontal', 'justify-content', 'flex-end') }} + {# Timeline Connector #} {{ style.css(id ~ ' .qx-timeline-connector', 'position', 'absolute') }} {{ style.css(id ~ ' .qx-timeline-connector', 'background-color', connectorColor) }} @@ -236,4 +253,4 @@ 100% { transform: translateX(0); opacity: 1; } } -{{ style.load(id) }} +{{ style.load(advanced.identifier.id) }} From f357cf1c513bd1a5abd31f6bdcd321f92c75854f Mon Sep 17 00:00:00 2001 From: SagorIslamOfficial Date: Wed, 10 Sep 2025 17:22:45 +0600 Subject: [PATCH 04/17] remove: entry animation from timeline items --- timeline/partials/style.twig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/timeline/partials/style.twig b/timeline/partials/style.twig index 85e1109..144d000 100644 --- a/timeline/partials/style.twig +++ b/timeline/partials/style.twig @@ -253,4 +253,9 @@ 100% { transform: translateX(0); opacity: 1; } } +{# Remove entry animation #} +{{ style.css(id ~ ' .qx-timeline-item', 'opacity', '1') }} +{{ style.css(id ~ ' .qx-timeline-item', 'transform', 'none') }} +{{ style.css(id ~ ' .qx-timeline-item', 'transition', 'none') }} + {{ style.load(advanced.identifier.id) }} From 32f8a3e0aedeb2f903d7685362b59b5509e0953a Mon Sep 17 00:00:00 2001 From: SagorIslamOfficial Date: Wed, 10 Sep 2025 19:10:50 +0600 Subject: [PATCH 05/17] feat: add connector position setting and styles for timeline --- timeline/config.yml | 14 +++++++++++ timeline/partials/html.twig | 2 ++ timeline/partials/style.twig | 49 ++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/timeline/config.yml b/timeline/config.yml index 5d0b974..2d8b62e 100644 --- a/timeline/config.yml +++ b/timeline/config.yml @@ -92,6 +92,20 @@ form: solid: Solid dashed: Dashed dotted: Dotted + + - name: connector_position + label: Connector Position + type: select + value: left + depends: + show_connector: true + options: + left: + label: Left Side + center: + label: Center + right: + label: Right Side styles: - name: timeline_appearance diff --git a/timeline/partials/html.twig b/timeline/partials/html.twig index 451614f..dd0570f 100644 --- a/timeline/partials/html.twig +++ b/timeline/partials/html.twig @@ -5,10 +5,12 @@ {% set alignment = general.layout_settings.alignment %} {% set showConnector = general.layout_settings.show_connector %} {% set connectorStyle = general.layout_settings.connector_style %} +{% set connectorPosition = general.layout_settings.connector_position %} {% set classes = classNames('qx-element qx-element-timeline-v2', 'qx-timeline-' ~ layout, 'qx-timeline-align-' ~ alignment, + 'qx-timeline-connector-pos-' ~ connectorPosition, visibilityClass(visibility), class ) %} diff --git a/timeline/partials/style.twig b/timeline/partials/style.twig index 144d000..26e2768 100644 --- a/timeline/partials/style.twig +++ b/timeline/partials/style.twig @@ -119,6 +119,55 @@ {{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'left', '-60px') }} {{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'top', '0') }} +{# Connector Position: Right Side #} +{{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical', 'padding-right', '60px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical', 'padding-left', '0') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical .qx-timeline-item .qx-timeline-connector', 'left', 'auto') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical .qx-timeline-item .qx-timeline-connector', 'right', '-46px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical .qx-timeline-marker', 'left', 'auto') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical .qx-timeline-marker', 'right', '-60px') }} + +{# Center Connector Position: Alternate items left/right #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical', 'padding-left', '0') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical', 'padding-right', '0') }} + +// Center connector placement: odd items on right edge, even on left edge +// Odd items (left column) +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-connector', 'left', '109%') }} + +// Even items (right column) +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-connector', 'left', '0') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-connector', 'transform', 'none') }} + +// Center marker placement +// Odd items (left column) +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-marker', 'left', '106.2%') }} + +// Even items (right column) +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'left', '0') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'transform', 'none') }} + +// Center layout: two-column float structure +// Set item width and box sizing +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item', 'width', '46%') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item', 'box-sizing', 'border-box') }} +// Float items: odd left, even right, clear both to start new row +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd)', 'float', 'left') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even)', 'float', 'right') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item', 'clear', 'both') }} + +// Alternate content alignment +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-content', 'text-align', 'right') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-content', 'text-align', 'left') }} + +// Align odd item images to the end (near connector) +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-image', 'display', 'flex') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-image', 'justify-content', 'flex-end') }} + +// Center connector layout: equal columns (46% each + 6% spacing) +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item', 'width', '46%') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item', 'box-sizing', 'border-box') }} + {# Horizontal Marker Position #} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'top', '-60px') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'left', '50%') }} From f6c136f5ce56907f40dfc52b895c750c05491ed6 Mon Sep 17 00:00:00 2001 From: SagorIslamOfficial Date: Thu, 11 Sep 2025 13:40:31 +0600 Subject: [PATCH 06/17] fix: update modal background apply styling to modal dialog --- modal-popup/partials/style.twig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modal-popup/partials/style.twig b/modal-popup/partials/style.twig index c0afeab..0ab7e21 100644 --- a/modal-popup/partials/style.twig +++ b/modal-popup/partials/style.twig @@ -414,7 +414,8 @@ {# Modal background color #} {% if modalBackground %} - {# Apply to modal content wrapper (the actual visible modal content) #} + {# Apply to modal dialog and content wrapper (the visible modal) #} + {{ style.background(ModalID ~ ' .qx-modal-dialog', modalBackground) }} {{ style.background(elementSelector, modalBackground) }} {{ style.background(elementSelector ~ '.classic-mode', modalBackground) }} {{ style.background(elementSelector ~ '.cover-mode', modalBackground) }} From 4807d78761b0aebac8aec3971515326809bb3485 Mon Sep 17 00:00:00 2001 From: SagorIslamOfficial Date: Mon, 22 Sep 2025 18:10:18 +0600 Subject: [PATCH 07/17] refactor: needed settings & complete `vertical` features --- timeline/config.yml | 554 +++++++++++++++++------------------ timeline/partials/html.twig | 39 +-- timeline/partials/style.twig | 273 +++++++++-------- 3 files changed, 429 insertions(+), 437 deletions(-) diff --git a/timeline/config.yml b/timeline/config.yml index 2d8b62e..c4bc0ec 100644 --- a/timeline/config.yml +++ b/timeline/config.yml @@ -3,280 +3,280 @@ slug: timeline groups: general helpId: timeline-element form: - general: - - name: timeline_items - label: Timeline Items - type: group-repeater - schema: - - name: title - label: Title - type: text - value: Timeline Item Title - - - name: date - label: Date/Time - type: text - value: January 2024 - help: Enter the date or time for this timeline item - - - name: description - label: Description - type: editor - value: Add your timeline item description here. - - - name: image - label: Image - type: media - filters: image - showstyle: true - - - name: icon - label: Icon - type: media - filters: icon - help: Choose an icon for this timeline item - - - name: link - label: Link - type: link - help: Optional link for the timeline item - - - name: active - label: Active State - type: switch - value: false - help: Mark this item as currently active/highlighted - - - name: layout_settings - label: Layout Settings - type: fields-group - status: open - schema: - - name: layout - label: Layout Type - type: select - value: vertical - options: - vertical: Vertical - horizontal: Horizontal - - - name: alignment - label: Timeline Alignment - type: choose - value: center - responsive: false - options: - left: - label: Left - icon: qxuicon-align-left - center: - label: Center - icon: qxuicon-align-center - right: - label: Right - icon: qxuicon-align-right - - - name: show_connector - label: Show Timeline Connector - type: switch - value: true - help: Display the connecting line between timeline items - - - name: connector_style - label: Connector Style - type: select - value: solid - depends: - show_connector: true - options: - solid: Solid - dashed: Dashed - dotted: Dotted - - - name: connector_position - label: Connector Position - type: select - value: left - depends: - show_connector: true - options: - left: - label: Left Side - center: - label: Center - right: - label: Right Side - - styles: - - name: timeline_appearance - label: Timeline Appearance - type: fields-group - status: open - schema: - - name: timeline_bg - label: Timeline Background - type: background - popover: true - - - name: connector_color - label: Connector Color - type: color - value: '#e5e5e5' - - - name: connector_width - label: Connector Width - type: slider - min: 1 - max: 10 - suffix: px - value: 2 - - - name: item_spacing - label: Item Spacing - type: slider - max: 100 - suffix: px - value: 20 - responsive: true - - - name: item_styles - label: Timeline Item Styles - type: fields-group - schema: - - name: item_bg - label: Item Background - type: background - popover: true - - - name: item_border - label: Item Border - type: border - popover: true - - - name: item_padding - label: Item Padding - type: dimensions - units: px - defaultUnit: px - value: - desktop: - top: 20 - bottom: 20 - left: 20 - right: 20 - - - name: item_shadow - label: Box Shadow - type: box-shadow - popover: true - - - name: icon_styles - label: Icon Styles - type: fields-group - schema: - - name: icon_bg - label: Icon Background - type: background - popover: true - - - name: icon_padding - label: Icon Padding - type: slider - min: 0 - max: 50 - suffix: px - value: 10 - - - name: icon_border_radius - label: Icon Border Radius - type: slider - min: 0 - max: 100 - suffix: % - value: 50 - - - name: content_styles - label: Content Styles - type: fields-group - schema: - - name: title_color - label: Title Color - type: color - value: '#333333' - - - name: title_font - label: Title Typography - type: typography - popover: true - - - name: date_color - label: Date Color - type: color - value: '#666666' - - - name: date_font - label: Date Typography - type: typography - popover: true - - - name: description_color - label: Description Color - type: color - value: '#666666' - - - name: description_font - label: Description Typography - type: typography - popover: true - - - name: hover_effects - label: Hover Effects - type: fields-group - schema: - - name: enable_hover - label: Enable Hover Effects - type: switch - value: true - - - name: hover_item_bg - label: Hover Item Background - type: background - popover: true - depends: - enable_hover: true - - - name: hover_title_color - label: Hover Title Color - type: color - depends: - enable_hover: true - - - name: hover_animation - label: Hover Animation - type: select - value: none - depends: - enable_hover: true - options: - none: None - fade: Fade - slide: Slide - scale: Scale - - - name: responsive_settings - label: Responsive Settings - type: fields-group - schema: - - name: mobile_layout - label: Mobile Layout - type: select - value: vertical - options: - vertical: Vertical - horizontal: Horizontal - - - name: hide_on_mobile - label: Hide Elements on Mobile - type: switch - value: false - help: Hide certain elements on mobile devices to save space + general: + - name: timeline_items + label: Timeline Items + type: group-repeater + schema: + - name: title + label: Title + type: text + value: Timeline Item Title + + - name: date + label: Date/Time + type: text + value: January 2025 + help: Enter the date or time for this timeline item + + - name: description + label: Description + type: editor + value: Add your timeline item description here. + + - name: image + label: Image + type: media + filters: image + showstyle: true + + - name: link + label: Link + type: link + help: Optional link for the timeline item + + - name: active + label: Active State + type: switch + value: false + help: Mark this item as currently active/highlighted + + - name: layout_settings + label: Layout Settings + type: fields-group + status: open + schema: + - name: layout + label: Layout Type + type: select + value: vertical + options: + vertical: Vertical + horizontal: Horizontal + + - name: alignment + label: Timeline Alignment + type: choose + value: center + responsive: false + options: + left: + label: Left + icon: qxuicon-align-left + center: + label: Center + icon: qxuicon-align-center + right: + label: Right + icon: qxuicon-align-right + + - name: show_connector + label: Show Timeline Connector + type: switch + value: true + help: Display the connecting line between timeline items + + - name: connector_style + label: Connector Style + type: select + value: solid + depends: + show_connector: true + options: + solid: Solid + dashed: Dashed + dotted: Dotted + + - name: connector_position + label: Connector Position + type: select + value: left + depends: + show_connector: true + options: + left: + label: Left Side + center: + label: Center + right: + label: Right Side + + styles: + - name: timeline_appearance + label: Timeline Appearance + type: fields-group + status: open + schema: + - name: timeline_bg + label: Timeline Background + type: background + popover: true + + - name: timeline_padding + label: Timeline Padding + type: padding + popover: true + + - name: timeline_border + label: Timeline Border + type: border + popover: true + + - name: connector_color + label: Connector Color + type: color + value: "#e5e5e5" + + - name: connector_width + label: Connector Width + type: slider + min: 1 + max: 10 + suffix: px + value: 2 + + - name: item_spacing + label: Item Spacing + type: slider + max: 100 + suffix: px + value: 20 + responsive: true + + - name: item_styles + label: Timeline Item Styles + type: fields-group + schema: + - name: item_bg + label: Item Background + type: background + popover: true + + - name: item_border + label: Item Border + type: border + popover: true + + - name: item_padding + label: Item Padding + type: dimensions + units: px + defaultUnit: px + value: + desktop: + top: 20 + bottom: 20 + left: 20 + right: 20 + + - name: item_shadow + label: Box Shadow + type: box-shadow + popover: true + + - name: content_styles + label: Content Styles + type: fields-group + schema: + - name: title_color + label: Title Color + type: color + value: "#333333" + + - name: title_font + label: Title Typography + type: typography + popover: true + + - name: date_color + label: Date Color + type: color + value: "#666666" + + - name: date_font + label: Date Typography + type: typography + popover: true + + - name: description_color + label: Description Color + type: color + value: "#666666" + + - name: description_font + label: Description Typography + type: typography + popover: true + + - name: hover_effects + label: Hover Effects + type: fields-group + schema: + - name: enable_hover + label: Enable Hover Effects + type: switch + value: true + + - name: hover_item_bg + label: Item Background + type: background + popover: true + depends: + enable_hover: true + + - name: hover_title_color + label: Title Color + type: color + depends: + enable_hover: true + + - name: hover_title_font + label: Title Typography + type: typography + popover: true + depends: + enable_hover: true + + - name: hover_date_color + label: Date Color + type: color + depends: + enable_hover: true + + - name: hover_date_font + label: Date Typography + type: typography + popover: true + depends: + enable_hover: true + + - name: hover_description_color + label: Description Color + type: color + depends: + enable_hover: true + + - name: hover_description_font + label: Description Typography + type: typography + popover: true + depends: + enable_hover: true + + - name: responsive_settings + label: Responsive Settings + type: fields-group + schema: + - name: mobile_layout + label: Mobile Layout + type: select + value: vertical + options: + vertical: Vertical + horizontal: Horizontal + + - name: hide_on_mobile + label: Hide Elements on Mobile + type: switch + value: false + help: Hide certain elements on mobile devices to save space diff --git a/timeline/partials/html.twig b/timeline/partials/html.twig index dd0570f..8ace65b 100644 --- a/timeline/partials/html.twig +++ b/timeline/partials/html.twig @@ -33,26 +33,15 @@
{% if layout == 'vertical' %}
+ {% if showConnector %} +
+ {% endif %} {% for item in timelineItems %}
- {% if showConnector %} -
- {% endif %}
- {% if item.icon.source %} - {% if item.icon.type == "svg" %} - {{ icon(item.icon.source) }} - {% else %} - Timeline icon - {% endif %} - {% else %} -
- {% endif %} +
- {% if item.icon.type == "svg" %} - {{ addIconStyle('#' ~ id ~ ' .qx-timeline-marker', item.icon) }} - {% endif %}
{% if item.date %} @@ -86,27 +75,15 @@
{% else %}
+ {% if showConnector %} +
+ {% endif %} {% for item in timelineItems %}
- {% if showConnector %} -
- {% endif %}
- {% if item.icon.source %} - {% if item.icon.type == "svg" %} - {{ icon(item.icon.source) }} - {% else %} - Timeline icon - {% endif %} - {% else %} -
- {% endif %} +
- {% if item.icon.type == "svg" %} - {{ addIconStyle('#' ~ id ~ ' .qx-timeline-marker', item.icon) }} - {% endif %} -
{% if item.date %}
{{ item.date }}
diff --git a/timeline/partials/style.twig b/timeline/partials/style.twig index 26e2768..76ba16d 100644 --- a/timeline/partials/style.twig +++ b/timeline/partials/style.twig @@ -2,6 +2,8 @@ {% set id = '#' ~ advanced.identifier.id %} {% set timelineBg = styles.timeline_appearance.timeline_bg %} +{% set timelinePadding = styles.timeline_appearance.timeline_padding %} +{% set timelineBorder = styles.timeline_appearance.timeline_border %} {% set connectorColor = styles.timeline_appearance.connector_color %} {# Extract slider values with responsive support #} @@ -14,11 +16,6 @@ {% set itemBorderRadius = styles.item_styles.item_border_radius %} {% set itemShadow = styles.item_styles.item_shadow %} -{% set iconBg = styles.icon_styles.icon_bg %} - -{# Extract slider values with responsive support #} -{% set iconPadding = (styles.icon_styles.icon_padding.desktop ?? styles.icon_styles.icon_padding.value ?? styles.icon_styles.icon_padding ?: 10) %} -{% set iconBorderRadius = (styles.icon_styles.icon_border_radius.desktop ?? styles.icon_styles.icon_border_radius.value ?? styles.icon_styles.icon_border_radius ?: 50) %} {% set titleColor = styles.content_styles.title_color %} {% set titleFont = styles.content_styles.title_font %} @@ -30,11 +27,20 @@ {% set enableHover = styles.hover_effects.enable_hover %} {% set hoverItemBg = styles.hover_effects.hover_item_bg %} {% set hoverTitleColor = styles.hover_effects.hover_title_color %} -{% set hoverAnimation = styles.hover_effects.hover_animation %} +{% set hoverTitleFont = styles.hover_effects.hover_title_font %} +{% set hoverDateColor = styles.hover_effects.hover_date_color %} +{% set hoverDateFont = styles.hover_effects.hover_date_font %} +{% set hoverDescriptionColor = styles.hover_effects.hover_description_color %} +{% set hoverDescriptionFont = styles.hover_effects.hover_description_font %} + +{% set mobileLayout = styles.responsive_settings.mobile_layout %} +{% set hideOnMobile = styles.responsive_settings.hide_on_mobile %} {# Timeline Wrapper #} {{ style.css(id ~ ' .qx-timeline-wrapper', 'position', 'relative') }} {{ style.background(id ~ ' .qx-timeline-wrapper', timelineBg) }} +{{ style.padding(id ~ ' .qx-timeline-wrapper', timelinePadding) }} +{{ style.border(id ~ ' .qx-timeline-wrapper', timelineBorder) }} {# Timeline Base Styles #} {{ style.css(id ~ ' .qx-timeline', 'position', 'relative') }} @@ -44,8 +50,14 @@ {# Timeline Alignment Styles #} {{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-left .qx-timeline-content', 'text-align', 'left') }} +{{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-left .qx-timeline-content', 'margin-left', '45px') }} +{{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-left .qx-timeline-content', 'margin-right', '45px') }} {{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-center .qx-timeline-content', 'text-align', 'center') }} +{{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-center .qx-timeline-content', 'margin-left', '45px') }} +{{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-center .qx-timeline-content', 'margin-right', '45px') }} {{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-right .qx-timeline-content', 'text-align', 'right') }} +{{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-right .qx-timeline-content', 'margin-right', '45px') }} +{{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-right .qx-timeline-content', 'margin-left', '45px') }} {# Updated: Center/left/right align images using flexbox #} {{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-left .qx-timeline-image', 'display', 'flex') }} @@ -77,96 +89,96 @@ {{ style.css(id ~ ' .qx-timeline-connector-dotted', 'border-color', connectorColor) }} {# Horizontal Connector Styles #} -{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item .qx-timeline-connector-dashed', 'border-width', connectorWidth ~ 'px 0') }} -{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item .qx-timeline-connector-dotted', 'border-width', connectorWidth ~ 'px 0') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector-dashed', 'border-width', connectorWidth ~ 'px 0') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector-dotted', 'border-width', connectorWidth ~ 'px 0') }} {# Vertical Timeline #} -{{ style.css(id ~ ' .qx-timeline-vertical', 'padding-left', '60px') }} -{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item .qx-timeline-connector', 'left', '-46px') }} -{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item .qx-timeline-connector', 'height', 'calc(100%)') }} -{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item .qx-timeline-connector', 'top', '20px') }} -{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item .qx-timeline-connector', 'width', connectorWidth ~ 'px') }} -{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item:last-child .qx-timeline-connector', 'height', 'calc(100% - 20px)') }} + +{# Global Vertical Connector Styling #} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-connector', 'position', 'absolute') }} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-connector', 'left', '-1px') }} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-connector', 'top', '10px') }} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-connector', 'height', '97.5%') }} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-connector', 'width', connectorWidth ~ 'px') }} {# Horizontal Timeline #} {{ style.css(id ~ ' .qx-timeline-horizontal', 'display', 'flex') }} {{ style.css(id ~ ' .qx-timeline-horizontal', 'justify-content', 'space-between') }} {{ style.css(id ~ ' .qx-timeline-horizontal', 'align-items', 'flex-start') }} -{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item .qx-timeline-connector', 'top', '0px') }} -{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item .qx-timeline-connector', 'left', '50%') }} -{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item .qx-timeline-connector', 'transform', 'translateX(-50%)') }} -{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item .qx-timeline-connector', 'width', '100%') }} -{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item .qx-timeline-connector', 'height', connectorWidth ~ 'px') }} + +{# Global Horizontal Connector Styling #} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'position', 'absolute') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'top', '-60px') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'left', '0') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'width', '100%') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'height', connectorWidth ~ 'px') }} {# Timeline Items #} {{ style.css(id ~ ' .qx-timeline-item', 'position', 'relative') }} {{ style.css(id ~ ' .qx-timeline-item', 'margin-bottom', '0') }} -{{ style.responsiveCss(id ~ ' .qx-timeline-item', itemSpacing, 'margin-bottom', 'px') }} -{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item:last-child', 'margin-bottom', '0') }} +{{ style.responsiveCss(id ~ ' .qx-timeline-item', itemSpacing, 'padding-bottom', 'px') }} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item:last-child', 'padding-bottom', '0') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'flex', '1') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item:not(:last-child)', 'margin-right', '20px') }} {# Timeline Marker #} {{ style.css(id ~ ' .qx-timeline-marker', 'position', 'absolute') }} -{{ style.css(id ~ ' .qx-timeline-marker', 'border-radius', iconBorderRadius ~ '%') }} -{{ style.css(id ~ ' .qx-timeline-marker', 'padding', iconPadding ~ 'px') }} {{ style.css(id ~ ' .qx-timeline-marker', 'display', 'flex') }} {{ style.css(id ~ ' .qx-timeline-marker', 'align-items', 'center') }} {{ style.css(id ~ ' .qx-timeline-marker', 'justify-content', 'center') }} {{ style.css(id ~ ' .qx-timeline-marker', 'z-index', '2') }} +{{ style.css(id ~ ' .qx-timeline-marker', 'width', '15px') }} +{{ style.css(id ~ ' .qx-timeline-marker', 'height', '15px') }} +{{ style.css(id ~ ' .qx-timeline-marker', 'top', '5px') }} {# Vertical Marker Position #} -{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'left', '-60px') }} + +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'transform', 'translateX(-50%)') }} {{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'top', '0') }} {# Connector Position: Right Side #} -{{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical', 'padding-right', '60px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical', 'padding-right', '0') }} {{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical', 'padding-left', '0') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical .qx-timeline-item .qx-timeline-connector', 'left', 'auto') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical .qx-timeline-item .qx-timeline-connector', 'right', '-46px') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical .qx-timeline-marker', 'left', 'auto') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical .qx-timeline-marker', 'right', '-60px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical .qx-timeline-connector', 'left', 'auto') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical .qx-timeline-connector', 'right', '-0.1px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical .qx-timeline-marker', 'right', '-14px') }} {# Center Connector Position: Alternate items left/right #} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical', 'padding-left', '0') }} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical', 'padding-right', '0') }} - -// Center connector placement: odd items on right edge, even on left edge -// Odd items (left column) -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-connector', 'left', '109%') }} - -// Even items (right column) -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-connector', 'left', '0') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-connector', 'transform', 'none') }} - -// Center marker placement -// Odd items (left column) -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-marker', 'left', '106.2%') }} - -// Even items (right column) -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'left', '0') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-connector', 'left', '50%') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-connector', 'transform', 'translateX(-50%)') }} + +{# Center layout: two-column structure #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item', 'width', '50%') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item', 'clear', 'none') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even)', 'float', 'left') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even)', 'margin-right', '1px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd)', 'float', 'right') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd)', 'clear', 'right') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical::after', 'content', '""') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical::after', 'display', 'table') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical::after', 'clear', 'both') }} + +{# Center marker placement #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'left', 'auto') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'right', '-7.5px') }} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'transform', 'none') }} - -// Center layout: two-column float structure -// Set item width and box sizing -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item', 'width', '46%') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item', 'box-sizing', 'border-box') }} -// Float items: odd left, even right, clear both to start new row -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd)', 'float', 'left') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even)', 'float', 'right') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item', 'clear', 'both') }} - -// Alternate content alignment -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-content', 'text-align', 'right') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-content', 'text-align', 'left') }} - -// Align odd item images to the end (near connector) -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-image', 'display', 'flex') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-image', 'justify-content', 'flex-end') }} - -// Center connector layout: equal columns (46% each + 6% spacing) -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item', 'width', '46%') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item', 'box-sizing', 'border-box') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-marker', 'left', '-7.5px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-marker', 'transform', 'none') }} + +{# Center content alignment #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-content', 'text-align', 'right') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-content', 'margin-right', '25px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-content', 'margin-left', '0') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-content', 'text-align', 'left') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-content', 'margin-left', '25px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-content', 'margin-right', '0') }} + +{# Center image alignment #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-image', 'display', 'flex') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-image', 'justify-content', 'flex-end') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-image', 'justify-content', 'flex-start') }} {# Horizontal Marker Position #} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'top', '-60px') }} @@ -174,13 +186,12 @@ {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'transform', 'translateX(-50%)') }} {# Timeline Dot (default marker) #} -{{ style.css(id ~ ' .qx-timeline-dot', 'width', '12px') }} -{{ style.css(id ~ ' .qx-timeline-dot', 'height', '12px') }} +{{ style.css(id ~ ' .qx-timeline-dot', 'width', '15px') }} +{{ style.css(id ~ ' .qx-timeline-dot', 'height', '15px') }} {{ style.css(id ~ ' .qx-timeline-dot', 'border-radius', '50%') }} {{ style.css(id ~ ' .qx-timeline-dot', 'background-color', connectorColor) }} {# Icon Styling #} -{{ style.background(id ~ ' .qx-timeline-marker', iconBg) }} {# Timeline Content #} {{ style.css(id ~ ' .qx-timeline-content', 'position', 'relative') }} @@ -199,7 +210,7 @@ {# Timeline Title #} {{ style.css(id ~ ' .qx-timeline-title', 'color', titleColor) }} -{{ style.css(id ~ ' .qx-timeline-title', 'margin-bottom', '12px') }} +{{ style.css(id ~ ' .qx-timeline-title', 'margin-bottom', '0') }} {{ style.css(id ~ ' .qx-timeline-title', 'margin-top', '0') }} {{ style.typography(id ~ ' .qx-timeline-title', titleFont) }} {{ style.css(id ~ ' .qx-timeline-title a', 'color', 'inherit') }} @@ -219,22 +230,17 @@ {{ style.css(id ~ ' .qx-timeline-description p:last-child', 'margin-bottom', '0') }} {# Active Item Styling #} -{{ style.css(id ~ ' .qx-timeline-item-active .qx-timeline-marker', 'transform', 'scale(1.2)') }} +{{ style.css(id ~ ' .qx-timeline-item-active .qx-timeline-marker', 'transform', 'translateX(-50%) scale(1.4)') }} {{ style.css(id ~ ' .qx-timeline-item-active .qx-timeline-dot', 'background-color', connectorColor) }} +{# Active Item Styling for Center Position #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item-active:nth-child(even) .qx-timeline-marker', 'transform', 'scale(1.4)') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item-active:nth-child(odd) .qx-timeline-marker', 'transform', 'scale(1.4)') }} + {# Hover Effects #} {% if enableHover %} {{ style.css(id ~ ' .qx-timeline-item', 'transition', 'all 0.3s ease') }} - {% if hoverAnimation == 'fade' %} - {{ style.css(id ~ ' .qx-timeline-item:hover', 'opacity', '0.8') }} - {% elseif hoverAnimation == 'scale' %} - {{ style.css(id ~ ' .qx-timeline-item:hover', 'transform', 'scale(1.05)') }} - {% elseif hoverAnimation == 'slide' %} - {{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item:hover', 'transform', 'translateX(10px)') }} - {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item:hover', 'transform', 'translateY(-10px)') }} - {% endif %} - {% if hoverItemBg %} {{ style.background(id ~ ' .qx-timeline-item:hover .qx-timeline-content', hoverItemBg) }} {% endif %} @@ -242,65 +248,74 @@ {% if hoverTitleColor %} {{ style.css(id ~ ' .qx-timeline-item:hover .qx-timeline-title', 'color', hoverTitleColor) }} {% endif %} -{% endif %} -{# Responsive Design #} -{{ style.phone(id ~ ' .qx-timeline-vertical', 'padding-left: 40px') }} -{{ style.phone(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'left: -40px') }} -{{ style.phone(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'width: 30px') }} -{{ style.phone(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'height: 30px') }} -{{ style.phone(id ~ ' .qx-timeline-vertical .qx-timeline-connector', 'left: 10px') }} - -{{ style.phone(id ~ ' .qx-timeline-horizontal', 'flex-direction: column') }} -{{ style.phone(id ~ ' .qx-timeline-horizontal', 'padding-top: 40px') }} -{{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'top: -40px') }} -{{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'left: -40px') }} -{{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'transform: none') }} -{{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'display: none') }} -{{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'margin-right: 0') }} + {% if hoverTitleFont %} + {{ style.typography(id ~ ' .qx-timeline-item:hover .qx-timeline-title', hoverTitleFont) }} + {% endif %} -{# Placeholder Styling #} -{{ style.css(id ~ ' .qx-timeline-placeholder', 'text-align', 'center') }} -{{ style.css(id ~ ' .qx-timeline-placeholder', 'padding', '40px') }} -{{ style.css(id ~ ' .qx-timeline-placeholder', 'color', '#999') }} -{{ style.css(id ~ ' .qx-timeline-placeholder p', 'margin', '0') }} + {% if hoverDateColor %} + {{ style.css(id ~ ' .qx-timeline-item:hover .qx-timeline-date', 'color', hoverDateColor) }} + {% endif %} -{# JavaScript Animation Classes #} -{{ style.css(id ~ ' .qx-timeline-item', 'opacity', '0') }} -{{ style.css(id ~ ' .qx-timeline-item', 'transform', 'translateY(30px)') }} -{{ style.css(id ~ ' .qx-timeline-item', 'transition', 'opacity 0.6s ease, transform 0.6s ease') }} + {% if hoverDateFont %} + {{ style.typography(id ~ ' .qx-timeline-item:hover .qx-timeline-date', hoverDateFont) }} + {% endif %} -{{ style.css(id ~ ' .qx-timeline-item-visible', 'opacity', '1') }} -{{ style.css(id ~ ' .qx-timeline-item-visible', 'transform', 'translateY(0)') }} + {% if hoverDescriptionColor %} + {{ style.css(id ~ ' .qx-timeline-item:hover .qx-timeline-description', 'color', hoverDescriptionColor) }} + {% endif %} -{% if enableHover and hoverAnimation != 'none' %} - {{ style.css(id ~ ' .qx-timeline-item-animated', 'animation-duration', '0.5s') }} - {{ style.css(id ~ ' .qx-timeline-item-animated', 'animation-fill-mode', 'both') }} + {% if hoverDescriptionFont %} + {{ style.typography(id ~ ' .qx-timeline-item:hover .qx-timeline-description', hoverDescriptionFont) }} + {% endif %} +{% endif %} - {% if hoverAnimation == 'fade' %} - {{ style.css(id ~ ' .qx-timeline-item-animated', 'animation-name', 'qx-timeline-fade-in') }} - {% elseif hoverAnimation == 'scale' %} - {{ style.css(id ~ ' .qx-timeline-item-animated', 'animation-name', 'qx-timeline-scale-in') }} - {% elseif hoverAnimation == 'slide' %} - {{ style.css(id ~ ' .qx-timeline-item-animated', 'animation-name', 'qx-timeline-slide-in') }} +{# Responsive Design #} +{% if hideOnMobile %} + {# Hide entire timeline on mobile when hideOnMobile is enabled #} + {{ style.phone(id ~ ' .qx-timeline-wrapper', 'display: none') }} +{% else %} + {# Mobile layout adjustments when timeline is visible #} + {% if mobileLayout == 'vertical' %} + {# Force vertical layout on mobile #} + {{ style.phone(id ~ ' .qx-timeline-horizontal', 'flex-direction: column') }} + {{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'margin-right: 0') }} + {{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'margin-bottom: ' ~ (itemSpacing.mobile ?? itemSpacing.value ?? itemSpacing ?: 20) ~ 'px') }} + + {# Vertical layout mobile optimizations #} + {{ style.phone(id ~ ' .qx-timeline-vertical', 'padding-left: 40px') }} + {{ style.phone(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'left: -40px') }} + {{ style.phone(id ~ ' .qx-timeline-vertical .qx-timeline-connector', 'left: 10px') }} + + {# Center connector mobile adjustments #} + {{ style.phone(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item', 'width: 100%') }} + {{ style.phone(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item', 'float: none') }} + {{ style.phone(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item', 'clear: both') }} + {{ style.phone(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item .qx-timeline-marker', 'left: -40px') }} + {{ style.phone(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item .qx-timeline-marker', 'right: auto') }} + {{ style.phone(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item .qx-timeline-content', 'text-align: left') }} + {{ style.phone(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item .qx-timeline-content', 'margin-left: 45px') }} + {{ style.phone(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item .qx-timeline-content', 'margin-right: 0') }} + {{ style.phone(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-connector', 'left: 10px') }} + {{ style.phone(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-connector', 'transform: none') }} + + {% elseif mobileLayout == 'horizontal' %} + {# Keep horizontal layout on mobile but optimize #} + {{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'top: -30px') }} + {{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'left: 50%') }} + {{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'transform: translateX(-50%)') }} + {{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'display: block') }} + {{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'top: 0') }} + {{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'height: 2px') }} + {{ style.phone(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'width: 100%') }} {% endif %} {% endif %} -{# Animation Keyframes #} -@keyframes qx-timeline-fade-in { - 0% { opacity: 0; } - 100% { opacity: 1; } -} - -@keyframes qx-timeline-scale-in { - 0% { transform: scale(0.8); opacity: 0; } - 100% { transform: scale(1); opacity: 1; } -} - -@keyframes qx-timeline-slide-in { - 0% { transform: translateX(-30px); opacity: 0; } - 100% { transform: translateX(0); opacity: 1; } -} +{# Placeholder Styling #} +{{ style.css(id ~ ' .qx-timeline-placeholder', 'text-align', 'center') }} +{{ style.css(id ~ ' .qx-timeline-placeholder', 'padding', '40px') }} +{{ style.css(id ~ ' .qx-timeline-placeholder', 'color', '#999') }} +{{ style.css(id ~ ' .qx-timeline-placeholder p', 'margin', '0') }} {# Remove entry animation #} {{ style.css(id ~ ' .qx-timeline-item', 'opacity', '1') }} From 49aeeca259f322f2905b8535e02337679cc9adb8 Mon Sep 17 00:00:00 2001 From: SagorIslamOfficial Date: Tue, 23 Sep 2025 12:20:14 +0600 Subject: [PATCH 08/17] fix: horizontal `alignment & connectors alignment` issues --- timeline/partials/style.twig | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/timeline/partials/style.twig b/timeline/partials/style.twig index 76ba16d..e5ffe73 100644 --- a/timeline/partials/style.twig +++ b/timeline/partials/style.twig @@ -67,9 +67,22 @@ {{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-right .qx-timeline-image', 'display', 'flex') }} {{ style.css(id ~ '.qx-timeline-vertical.qx-timeline-align-right .qx-timeline-image', 'justify-content', 'flex-end') }} -{{ style.css(id ~ '.qx-timeline-align-left .qx-timeline-horizontal', 'justify-content', 'flex-start') }} -{{ style.css(id ~ '.qx-timeline-align-center .qx-timeline-horizontal', 'justify-content', 'center') }} -{{ style.css(id ~ '.qx-timeline-align-right .qx-timeline-horizontal', 'justify-content', 'flex-end') }} +{{ style.css(id ~ ' .qx-timeline-horizontal.qx-timeline-align-left', 'justify-content', 'flex-start') }} +{{ style.css(id ~ ' .qx-timeline-horizontal.qx-timeline-align-center', 'justify-content', 'center') }} +{{ style.css(id ~ ' .qx-timeline-horizontal.qx-timeline-align-right', 'justify-content', 'flex-end') }} + +{# Horizontal Content Alignment #} +{{ style.css(id ~ '.qx-timeline-horizontal.qx-timeline-align-left .qx-timeline-content', 'text-align', 'left') }} +{{ style.css(id ~ '.qx-timeline-horizontal.qx-timeline-align-center .qx-timeline-content', 'text-align', 'center') }} +{{ style.css(id ~ '.qx-timeline-horizontal.qx-timeline-align-right .qx-timeline-content', 'text-align', 'right') }} + +{# Horizontal Image Alignment #} +{{ style.css(id ~ '.qx-timeline-horizontal.qx-timeline-align-left .qx-timeline-image', 'display', 'flex') }} +{{ style.css(id ~ '.qx-timeline-horizontal.qx-timeline-align-left .qx-timeline-image', 'justify-content', 'flex-start') }} +{{ style.css(id ~ '.qx-timeline-horizontal.qx-timeline-align-center .qx-timeline-image', 'display', 'flex') }} +{{ style.css(id ~ '.qx-timeline-horizontal.qx-timeline-align-center .qx-timeline-image', 'justify-content', 'center') }} +{{ style.css(id ~ '.qx-timeline-horizontal.qx-timeline-align-right .qx-timeline-image', 'display', 'flex') }} +{{ style.css(id ~ '.qx-timeline-horizontal.qx-timeline-align-right .qx-timeline-image', 'justify-content', 'flex-end') }} {# Timeline Connector #} {{ style.css(id ~ ' .qx-timeline-connector', 'position', 'absolute') }} @@ -107,8 +120,7 @@ {{ style.css(id ~ ' .qx-timeline-horizontal', 'align-items', 'flex-start') }} {# Global Horizontal Connector Styling #} -{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'position', 'absolute') }} -{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'top', '-60px') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'top', '-45px') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'left', '0') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'width', '100%') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'height', connectorWidth ~ 'px') }} @@ -181,7 +193,7 @@ {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-image', 'justify-content', 'flex-start') }} {# Horizontal Marker Position #} -{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'top', '-60px') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'top', '-52px') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'left', '50%') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'transform', 'translateX(-50%)') }} From c122f72d8a446924e7841102b2ac0ca8e8e28aec Mon Sep 17 00:00:00 2001 From: SagorIslamOfficial Date: Mon, 29 Sep 2025 11:51:20 +0600 Subject: [PATCH 09/17] feat: implement horizontal timeline slider with navigation --- timeline/config.yml | 44 +++++++++++ timeline/partials/html.twig | 90 ++++++++++++++-------- timeline/partials/script.twig | 140 ++++++++++++++++++++++++++++++++++ timeline/partials/style.twig | 97 +++++++++++++++++++++-- 4 files changed, 334 insertions(+), 37 deletions(-) diff --git a/timeline/config.yml b/timeline/config.yml index c4bc0ec..423e65b 100644 --- a/timeline/config.yml +++ b/timeline/config.yml @@ -93,6 +93,7 @@ form: value: left depends: show_connector: true + layout: vertical options: left: label: Left Side @@ -101,6 +102,49 @@ form: right: label: Right Side + - name: items_per_view + label: Items Per View + type: slider + min: 1 + max: 6 + value: 3 + responsive: true + help: Number of timeline items to show at once in horizontal layout and value must be between 1 and 6. + depends: + layout: horizontal + + - name: slider_mode + label: Slider Mode + type: select + value: manual + depends: + layout: horizontal + options: + manual: Manual + auto: Auto Play + + - name: auto_play_speed + label: Auto Play Speed (seconds) + type: slider + min: 1 + max: 10 + suffix: s + value: 3 + depends: + layout: horizontal + slider_mode: auto + + - name: show_navigation + label: Show Navigation Arrows + type: select + value: hover + depends: + layout: horizontal + options: + always: Always Visible + hover: Show on Hover + disabled: Disabled + styles: - name: timeline_appearance label: Timeline Appearance diff --git a/timeline/partials/html.twig b/timeline/partials/html.twig index 8ace65b..93b6fb0 100644 --- a/timeline/partials/html.twig +++ b/timeline/partials/html.twig @@ -74,45 +74,73 @@ {% endfor %}
{% else %} -
- {% if showConnector %} -
+ {% set itemsPerView = 3 %} + {% if general.layout_settings.items_per_view %} + {% if general.layout_settings.items_per_view.desktop %} + {% set itemsPerView = general.layout_settings.items_per_view.desktop %} + {% else %} + {% set itemsPerView = general.layout_settings.items_per_view %} {% endif %} - {% for item in timelineItems %} -
-
-
-
+ {% endif %} +
+ +
+ {% if showConnector %} +
+ + + {% endif %} + +
+ {% for item in timelineItems %} +
+
+
+
-
- {% if item.date %} -
{{ item.date }}
- {% endif %} +
+ {% if item.date %} +
{{ item.date }}
+ {% endif %} - {% if item.title %} -

- {% if item.link.url %} - {{ item.title | link(item.link) }} - {% else %} - {{ item.title }} + {% if item.title %} +

+ {% if item.link.url %} + {{ item.title | link(item.link) }} + {% else %} + {{ item.title }} + {% endif %} +

{% endif %} - - {% endif %} - {% if item.image.source %} -
- {{ image(item.image.source, item.title, '', '', item.image) }} -
- {% endif %} + {% if item.image.source %} +
+ {{ image(item.image.source, item.title, '', '', item.image) }} +
+ {% endif %} - {% if item.description %} -
- {{ item.description|raw }} + {% if item.description %} +
+ {{ item.description|raw }} +
+ {% endif %}
- {% endif %} -
+
+ {% endfor %}
- {% endfor %} +
{% endif %}
diff --git a/timeline/partials/script.twig b/timeline/partials/script.twig index b9350f8..d1f6671 100644 --- a/timeline/partials/script.twig +++ b/timeline/partials/script.twig @@ -4,6 +4,10 @@ {% if mode != 'builder' %} jQuery(document).ready(function($) { +{% endif %} +{% if mode == 'builder' %} +jQuery(function($) { +{% endif %} var timelineId = "{{ id }}"; var $timeline = $('#' + timelineId); @@ -68,11 +72,147 @@ jQuery(document).ready(function($) { } }); + function initTimelineSlider() { + var $horizontalTimeline = $timeline.find('.qx-timeline-horizontal'); + + if ($horizontalTimeline.length) { + var $track = $horizontalTimeline.find('.qx-timeline-slider-track'); + var $items = $track.find('.qx-timeline-item'); + var $prevBtn = $horizontalTimeline.find('.qx-timeline-nav-prev'); + var $nextBtn = $horizontalTimeline.find('.qx-timeline-nav-next'); + + var itemsPerView = parseInt($horizontalTimeline.data('items-per-view')) || 3; + var sliderMode = $horizontalTimeline.data('slider-mode') || 'manual'; + var autoSpeed = parseInt($horizontalTimeline.data('auto-speed')) || 3; + var currentSlide = 0; + var totalSlides = Math.ceil($items.length / itemsPerView); + var autoPlayInterval; + + function updateSliderWidth() { + var containerWidth = $horizontalTimeline.find('.qx-timeline-slider-container').width(); + var spacing = 25; + var itemWidth = (containerWidth - (itemsPerView - 1) * spacing) / itemsPerView; + + $items.each(function() { + $(this).css({ + 'width': itemWidth + 'px', + 'flex-shrink': '0', + 'flex-grow': '0' + }); + }); + + var trackWidth = ($items.length * itemWidth) + (($items.length - 1) * spacing); + $track.css('width', trackWidth + 'px'); + } + + function goToSlide(slideIndex) { + if (slideIndex < 0) slideIndex = totalSlides - 1; + if (slideIndex >= totalSlides) slideIndex = 0; + + currentSlide = slideIndex; + var containerWidth = $horizontalTimeline.find('.qx-timeline-slider-container').width(); + var spacing = 25; + var itemWidth = (containerWidth - (itemsPerView - 1) * spacing) / itemsPerView; + var moveDistance = slideIndex * (itemWidth + spacing) * itemsPerView; + $track.css('transform', 'translateX(-' + moveDistance + 'px)'); + + $prevBtn.toggleClass('disabled', slideIndex === 0); + $nextBtn.toggleClass('disabled', slideIndex === totalSlides - 1); + } + + function nextSlide() { + goToSlide(currentSlide + 1); + } + + function prevSlide() { + goToSlide(currentSlide - 1); + } + + function startAutoPlay() { + if (sliderMode === 'auto') { + autoPlayInterval = setInterval(function() { + nextSlide(); + }, autoSpeed * 1000); + } + } + + function stopAutoPlay() { + if (autoPlayInterval) { + clearInterval(autoPlayInterval); + } + } + + $prevBtn.on('click', function() { + stopAutoPlay(); + prevSlide(); + startAutoPlay(); + }); + + $nextBtn.on('click', function() { + stopAutoPlay(); + nextSlide(); + startAutoPlay(); + }); + + $horizontalTimeline.on('mouseenter', function() { + stopAutoPlay(); + }).on('mouseleave', function() { + startAutoPlay(); + }); + + var startX = 0; + var currentX = 0; + var isDragging = false; + + $track.on('touchstart mousedown', function(e) { + stopAutoPlay(); + isDragging = true; + startX = e.type === 'touchstart' ? e.originalEvent.touches[0].clientX : e.clientX; + e.preventDefault(); + }); + + $(document).on('touchmove mousemove', function(e) { + if (!isDragging) return; + currentX = e.type === 'touchmove' ? e.originalEvent.touches[0].clientX : e.clientX; + e.preventDefault(); + }); + + $(document).on('touchend mouseup', function() { + if (!isDragging) return; + isDragging = false; + + var diffX = startX - currentX; + var threshold = 50; + + if (Math.abs(diffX) > threshold) { + if (diffX > 0) { + nextSlide(); + } else { + prevSlide(); + } + } + startAutoPlay(); + }); + + updateSliderWidth(); + goToSlide(0); + startAutoPlay(); + + $(window).on('resize', function() { + updateSliderWidth(); + }); + } + } + initTimelineAnimations(); + initTimelineSlider(); handleHashNavigation(); $(window).on('resize', function() { setTimeout(initTimelineAnimations, 300); }); }); +{% if mode != 'builder' %} +{% endif %} +{% if mode == 'builder' %} {% endif %} diff --git a/timeline/partials/style.twig b/timeline/partials/style.twig index e5ffe73..6cb3d10 100644 --- a/timeline/partials/style.twig +++ b/timeline/partials/style.twig @@ -115,23 +115,108 @@ {{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-connector', 'width', connectorWidth ~ 'px') }} {# Horizontal Timeline #} -{{ style.css(id ~ ' .qx-timeline-horizontal', 'display', 'flex') }} -{{ style.css(id ~ ' .qx-timeline-horizontal', 'justify-content', 'space-between') }} -{{ style.css(id ~ ' .qx-timeline-horizontal', 'align-items', 'flex-start') }} +{{ style.css(id ~ ' .qx-timeline-horizontal', 'position', 'relative') }} + +{# Horizontal Slider Container #} +{{ style.css(id ~ ' .qx-timeline-slider-container', 'overflow', 'hidden') }} +{{ style.css(id ~ ' .qx-timeline-slider-container', 'width', '100%') }} +{{ style.css(id ~ ' .qx-timeline-slider-container', 'padding-top', '80px') }} + +{# Horizontal Slider Track #} +{{ style.css(id ~ ' .qx-timeline-slider-track', 'display', 'flex') }} +{{ style.css(id ~ ' .qx-timeline-slider-track', 'transition', 'transform 0.3s ease') }} +{{ style.css(id ~ ' .qx-timeline-slider-track', 'align-items', 'flex-start') }} +{{ style.css(id ~ ' .qx-timeline-slider-track', 'width', 'auto') }} +{% if mode != 'builder' %} +{{ style.css(id ~ ' .qx-timeline-slider-track', 'gap', '25px') }} +{% endif %} + +{# Builder Mode Fixes #} +{% if mode == 'builder' %} +{% set itemsPerView = 3 %} +{% if general.layout_settings.items_per_view %} + {% if general.layout_settings.items_per_view.desktop %} + {% set itemsPerView = general.layout_settings.items_per_view.desktop %} + {% else %} + {% set itemsPerView = general.layout_settings.items_per_view %} + {% endif %} +{% endif %} +{% set gapSize = (itemsPerView - 1) * 25 %} + +{{ style.css(id ~ ' .qx-timeline-horizontal', 'min-height', '280px') }} +{{ style.css(id ~ ' .qx-timeline-slider-container', 'min-height', '280px') }} +{{ style.css(id ~ ' .qx-timeline-slider-container', 'overflow', 'hidden') }} +{{ style.css(id ~ ' .qx-timeline-slider-track', 'flex-wrap', 'nowrap') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'min-width', 'unset') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'max-width', 'unset') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'flex', '0 0 auto') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'margin-right', '0') }} + +{# Current setting-based styles #} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'width', 'calc((100% - ' ~ gapSize ~ 'px) / ' ~ itemsPerView ~ ')') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item:not(:last-child)', 'margin-right', '25px') }} + +{# Also add data-attribute-based styles as fallback/override #} +{{ style.css(id ~ ' .qx-timeline-horizontal[data-items-per-view="1"] .qx-timeline-item', 'width', 'calc(100% - 0px)') }} +{{ style.css(id ~ ' .qx-timeline-horizontal[data-items-per-view="2"] .qx-timeline-item', 'width', 'calc((100% - 25px) / 2)') }} +{{ style.css(id ~ ' .qx-timeline-horizontal[data-items-per-view="3"] .qx-timeline-item', 'width', 'calc((100% - 50px) / 3)') }} +{{ style.css(id ~ ' .qx-timeline-horizontal[data-items-per-view="4"] .qx-timeline-item', 'width', 'calc((100% - 75px) / 4)') }} +{{ style.css(id ~ ' .qx-timeline-horizontal[data-items-per-view="5"] .qx-timeline-item', 'width', 'calc((100% - 100px) / 5)') }} +{{ style.css(id ~ ' .qx-timeline-horizontal[data-items-per-view="6"] .qx-timeline-item', 'width', 'calc((100% - 125px) / 6)') }} +{% endif %} {# Global Horizontal Connector Styling #} -{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'top', '-45px') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'position', 'absolute') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'top', '35px') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'left', '0') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'width', '100%') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-connector', 'height', connectorWidth ~ 'px') }} +{# Horizontal Navigation Arrows #} +{{ style.css(id ~ ' .qx-timeline-nav', 'position', 'absolute') }} +{{ style.css(id ~ ' .qx-timeline-nav', 'top', '20px') }} +{{ style.css(id ~ ' .qx-timeline-nav', 'z-index', '10') }} +{{ style.css(id ~ ' .qx-timeline-nav', 'background', 'rgba(0, 0, 0, 0.7)') }} +{{ style.css(id ~ ' .qx-timeline-nav', 'color', 'white') }} +{{ style.css(id ~ ' .qx-timeline-nav', 'border', 'none') }} +{{ style.css(id ~ ' .qx-timeline-nav', 'border-radius', '50%') }} +{{ style.css(id ~ ' .qx-timeline-nav', 'width', '32px') }} +{{ style.css(id ~ ' .qx-timeline-nav', 'height', '32px') }} +{{ style.css(id ~ ' .qx-timeline-nav', 'display', 'flex') }} +{{ style.css(id ~ ' .qx-timeline-nav', 'align-items', 'center') }} +{{ style.css(id ~ ' .qx-timeline-nav', 'justify-content', 'center') }} +{{ style.css(id ~ ' .qx-timeline-nav', 'cursor', 'pointer') }} +{{ style.css(id ~ ' .qx-timeline-nav', 'transition', 'all 0.3s ease') }} +{{ style.css(id ~ ' .qx-timeline-nav', 'opacity', '0') }} +{{ style.css(id ~ ' .qx-timeline-nav', 'pointer-events', 'none') }} + +{{ style.css(id ~ ' .qx-timeline-nav-prev', 'left', '-13px') }} +{{ style.css(id ~ ' .qx-timeline-nav-next', 'right', '-13px') }} + +{{ style.css(id ~ ' .qx-timeline-nav:hover', 'background', 'rgba(0, 0, 0, 0.9)') }} + +{# Navigation Visibility States #} +{{ style.css(id ~ ' .qx-timeline-horizontal[data-show-nav="always"] .qx-timeline-nav', 'opacity', '1') }} +{{ style.css(id ~ ' .qx-timeline-horizontal[data-show-nav="always"] .qx-timeline-nav', 'pointer-events', 'auto') }} + +{# Disabled state #} +{{ style.css(id ~ ' .qx-timeline-horizontal[data-show-nav="disabled"] .qx-timeline-nav', 'opacity', '0') }} +{{ style.css(id ~ ' .qx-timeline-horizontal[data-show-nav="disabled"] .qx-timeline-nav', 'pointer-events', 'none') }} + +{# Hover state #} +{{ style.css(id ~ ' .qx-timeline-horizontal[data-show-nav="hover"] .qx-timeline-nav', 'opacity', '0 !important') }} +{{ style.css(id ~ ' .qx-timeline-horizontal[data-show-nav="hover"] .qx-timeline-nav', 'pointer-events', 'none') }} +{{ style.css(id ~ ' .qx-timeline-horizontal[data-show-nav="hover"]:hover .qx-timeline-nav', 'opacity', '1 !important') }} +{{ style.css(id ~ ' .qx-timeline-horizontal[data-show-nav="hover"]:hover .qx-timeline-nav', 'pointer-events', 'auto') }} + + {# Timeline Items #} {{ style.css(id ~ ' .qx-timeline-item', 'position', 'relative') }} {{ style.css(id ~ ' .qx-timeline-item', 'margin-bottom', '0') }} {{ style.responsiveCss(id ~ ' .qx-timeline-item', itemSpacing, 'padding-bottom', 'px') }} {{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item:last-child', 'padding-bottom', '0') }} -{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'flex', '1') }} -{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item:not(:last-child)', 'margin-right', '20px') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'flex', '0 0 auto') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'min-width', '0') }} {# Timeline Marker #} {{ style.css(id ~ ' .qx-timeline-marker', 'position', 'absolute') }} From e54e08ba01488ef9de679f4864ddf79504789c6c Mon Sep 17 00:00:00 2001 From: SagorIslamOfficial Date: Mon, 29 Sep 2025 13:12:13 +0600 Subject: [PATCH 10/17] feat: add connector bootm position settings for horizontal layout --- timeline/config.yml | 16 ++++++++++++ timeline/partials/html.twig | 6 ++++- timeline/partials/style.twig | 50 ++++++++++++++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/timeline/config.yml b/timeline/config.yml index 423e65b..ef38fa2 100644 --- a/timeline/config.yml +++ b/timeline/config.yml @@ -102,6 +102,22 @@ form: right: label: Right Side + - name: connector_position_horizontal + label: Connector Position + type: select + value: top + help: Position of the connector line in horizontal layout + depends: + show_connector: true + layout: horizontal + options: + top: + label: Top + center: + label: Center + bottom: + label: Bottom + - name: items_per_view label: Items Per View type: slider diff --git a/timeline/partials/html.twig b/timeline/partials/html.twig index 93b6fb0..098954f 100644 --- a/timeline/partials/html.twig +++ b/timeline/partials/html.twig @@ -6,11 +6,15 @@ {% set showConnector = general.layout_settings.show_connector %} {% set connectorStyle = general.layout_settings.connector_style %} {% set connectorPosition = general.layout_settings.connector_position %} +{% set connectorPositionHorizontal = general.layout_settings.connector_position_horizontal %} + +{# Use appropriate connector position based on layout #} +{% set finalConnectorPosition = layout == 'horizontal' ? connectorPositionHorizontal : connectorPosition %} {% set classes = classNames('qx-element qx-element-timeline-v2', 'qx-timeline-' ~ layout, 'qx-timeline-align-' ~ alignment, - 'qx-timeline-connector-pos-' ~ connectorPosition, + 'qx-timeline-connector-pos-' ~ finalConnectorPosition, visibilityClass(visibility), class ) %} diff --git a/timeline/partials/style.twig b/timeline/partials/style.twig index 6cb3d10..59a669a 100644 --- a/timeline/partials/style.twig +++ b/timeline/partials/style.twig @@ -125,7 +125,7 @@ {# Horizontal Slider Track #} {{ style.css(id ~ ' .qx-timeline-slider-track', 'display', 'flex') }} {{ style.css(id ~ ' .qx-timeline-slider-track', 'transition', 'transform 0.3s ease') }} -{{ style.css(id ~ ' .qx-timeline-slider-track', 'align-items', 'flex-start') }} +{{ style.css(id ~ ' .qx-timeline-slider-track', 'align-items', 'stretch') }} {{ style.css(id ~ ' .qx-timeline-slider-track', 'width', 'auto') }} {% if mode != 'builder' %} {{ style.css(id ~ ' .qx-timeline-slider-track', 'gap', '25px') }} @@ -213,10 +213,13 @@ {# Timeline Items #} {{ style.css(id ~ ' .qx-timeline-item', 'position', 'relative') }} {{ style.css(id ~ ' .qx-timeline-item', 'margin-bottom', '0') }} -{{ style.responsiveCss(id ~ ' .qx-timeline-item', itemSpacing, 'padding-bottom', 'px') }} +{{ style.responsiveCss(id ~ ' .qx-timeline-vertical .qx-timeline-item', itemSpacing, 'padding-bottom', 'px') }} {{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-item:last-child', 'padding-bottom', '0') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'flex', '0 0 auto') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'min-width', '0') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'padding-bottom', '0') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'display', 'flex') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'flex-direction', 'column') }} {# Timeline Marker #} {{ style.css(id ~ ' .qx-timeline-marker', 'position', 'absolute') }} @@ -282,6 +285,39 @@ {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'left', '50%') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'transform', 'translateX(-50%)') }} +{# Horizontal Connector Position: Center System #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-connector', 'top', '50%') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-connector', 'transform', 'translateY(-50%)') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-slider-container', 'padding-top', '120px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-slider-container', 'padding-bottom', '40px') }} + +{# Center layout: alternating top/bottom items #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even)', 'align-self', 'flex-end') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even)', 'margin-bottom', '60px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd)', 'align-self', 'flex-start') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd)', 'margin-top', '60px') }} + +{# Center marker positioning for horizontal #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'top', 'auto') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'bottom', '-60px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd) .qx-timeline-marker', 'top', '-60px') }} + +{# Horizontal Connector Position: Top System #} +{{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-connector', 'top', '35px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-marker', 'top', '-52px') }} + +{# Horizontal Connector Position: Bottom System #} +{{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-connector', 'top', 'auto') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-connector', 'bottom', '10px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-marker', 'top', 'auto') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-marker', 'bottom', '-55px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-slider-container', 'padding-top', '0px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-slider-container', 'padding-bottom', '60px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-nav-prev', 'bottom', '-5px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-nav-prev', 'top', 'unset') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-nav-next', 'bottom', '-5px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-nav-next', 'top', 'unset') }} + {# Timeline Dot (default marker) #} {{ style.css(id ~ ' .qx-timeline-dot', 'width', '15px') }} {{ style.css(id ~ ' .qx-timeline-dot', 'height', '15px') }} @@ -298,6 +334,16 @@ {{ style.borderRadius(id ~ ' .qx-timeline-content', itemBorderRadius) }} {{ style.boxShadow(id ~ ' .qx-timeline-content', itemShadow) }} +{# Make horizontal timeline content fill full height #} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-content', 'height', '100%') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-content', 'display', 'flex') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-content', 'flex-direction', 'column') }} + +{# Content alignment based on connector position #} +{{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-content', 'justify-content', 'flex-start') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-content', 'justify-content', 'flex-end') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-content', 'justify-content', 'flex-end') }} + {# Timeline Date #} {{ style.css(id ~ ' .qx-timeline-date', 'color', dateColor) }} {{ style.css(id ~ ' .qx-timeline-date', 'margin-bottom', '8px') }} From b7d6fd42697ce4c647c1ca0f736851b13a1346f7 Mon Sep 17 00:00:00 2001 From: SagorIslamOfficial Date: Mon, 29 Sep 2025 14:07:02 +0600 Subject: [PATCH 11/17] feat: reverse content order for bottom connector position in horizontal timeline --- timeline/partials/style.twig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/timeline/partials/style.twig b/timeline/partials/style.twig index 59a669a..460d6a0 100644 --- a/timeline/partials/style.twig +++ b/timeline/partials/style.twig @@ -339,10 +339,12 @@ {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-content', 'display', 'flex') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-content', 'flex-direction', 'column') }} +{# Reverse content order only for bottom connector position #} +{{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-content', 'flex-direction', 'column-reverse') }} + {# Content alignment based on connector position #} {{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-content', 'justify-content', 'flex-start') }} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-content', 'justify-content', 'flex-end') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-content', 'justify-content', 'flex-end') }} {# Timeline Date #} {{ style.css(id ~ ' .qx-timeline-date', 'color', dateColor) }} From 00046d8be533149297b56389c7458928ed125eb7 Mon Sep 17 00:00:00 2001 From: SagorIslamOfficial Date: Mon, 29 Sep 2025 16:09:32 +0600 Subject: [PATCH 12/17] feat: update horizontal timeline styles for connector positioning and navigation --- timeline/partials/style.twig | 75 +++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/timeline/partials/style.twig b/timeline/partials/style.twig index 460d6a0..02bfd8f 100644 --- a/timeline/partials/style.twig +++ b/timeline/partials/style.twig @@ -118,9 +118,9 @@ {{ style.css(id ~ ' .qx-timeline-horizontal', 'position', 'relative') }} {# Horizontal Slider Container #} -{{ style.css(id ~ ' .qx-timeline-slider-container', 'overflow', 'hidden') }} +{{ style.css(id ~ ' .qx-timeline-slider-container', 'overflow', 'clip') }} {{ style.css(id ~ ' .qx-timeline-slider-container', 'width', '100%') }} -{{ style.css(id ~ ' .qx-timeline-slider-container', 'padding-top', '80px') }} +{{ style.css(id ~ ' .qx-timeline-slider-container', 'padding-top', '55px') }} {# Horizontal Slider Track #} {{ style.css(id ~ ' .qx-timeline-slider-track', 'display', 'flex') }} @@ -190,9 +190,6 @@ {{ style.css(id ~ ' .qx-timeline-nav', 'opacity', '0') }} {{ style.css(id ~ ' .qx-timeline-nav', 'pointer-events', 'none') }} -{{ style.css(id ~ ' .qx-timeline-nav-prev', 'left', '-13px') }} -{{ style.css(id ~ ' .qx-timeline-nav-next', 'right', '-13px') }} - {{ style.css(id ~ ' .qx-timeline-nav:hover', 'background', 'rgba(0, 0, 0, 0.9)') }} {# Navigation Visibility States #} @@ -209,7 +206,6 @@ {{ style.css(id ~ ' .qx-timeline-horizontal[data-show-nav="hover"]:hover .qx-timeline-nav', 'opacity', '1 !important') }} {{ style.css(id ~ ' .qx-timeline-horizontal[data-show-nav="hover"]:hover .qx-timeline-nav', 'pointer-events', 'auto') }} - {# Timeline Items #} {{ style.css(id ~ ' .qx-timeline-item', 'position', 'relative') }} {{ style.css(id ~ ' .qx-timeline-item', 'margin-bottom', '0') }} @@ -285,36 +281,68 @@ {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'left', '50%') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'transform', 'translateX(-50%)') }} -{# Horizontal Connector Position: Center System #} +{# Horizontal Connector Position: Center #} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-connector', 'top', '50%') }} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-connector', 'transform', 'translateY(-50%)') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-slider-container', 'padding-top', '120px') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-slider-container', 'padding-bottom', '40px') }} -{# Center layout: alternating top/bottom items #} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even)', 'align-self', 'flex-end') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even)', 'margin-bottom', '60px') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd)', 'align-self', 'flex-start') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd)', 'margin-top', '60px') }} +{# Center Position Specific Overrides #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-slider-container', 'overflow-x', 'clip') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-slider-container', 'overflow-y', 'visible') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-slider-container', 'padding-top', '0') }} + +{# Keep stretch alignment for equal heights but use transforms for positioning #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-slider-track', 'align-items', 'stretch') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-slider-track', 'position', 'relative') }} -{# Center marker positioning for horizontal #} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'top', 'auto') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'bottom', '-60px') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd) .qx-timeline-marker', 'top', '-60px') }} +{# Odd items: above center line #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd)', 'transform', 'translateY(-170px)') }} -{# Horizontal Connector Position: Top System #} -{{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-connector', 'top', '35px') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-marker', 'top', '-52px') }} +{# Even items: below center line #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even)', 'transform', 'translateY(170px)') }} + +{# Markers positioned ON the connector line #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd) .qx-timeline-marker', 'top', 'auto') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd) .qx-timeline-marker', 'bottom', '-50px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'top', '-50px') }} + +{% if mode == 'builder' %} +{# Builder mode center position marker fixes - these override the general styles above #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd)', 'transform', 'translateY(-150px)') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd) .qx-timeline-marker', 'bottom', '-42px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'top', '-38px') }} +{% endif %} -{# Horizontal Connector Position: Bottom System #} +{# Horizontal Connector Position: Top #} +{{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-connector', 'top', '12px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-marker', 'top', '-50px') }} + +{# Horizontal Top Position Navigation #} +{{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-nav-prev', 'left', '-13px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-nav-prev', 'top', '-3px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-nav-next', 'right', '-13px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-nav-next', 'top', '-3px') }} + +{# Horizontal Center Position Navigation #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-nav-prev', 'left', '-13px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-nav-prev', 'top', '0.001px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-nav-prev', 'bottom', '0.001px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-nav-prev', 'margin', 'auto') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-nav-next', 'right', '-13px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-nav-next', 'top', '0.001px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-nav-next', 'bottom', '0.001px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-nav-next', 'margin', 'auto') }} + +{# Horizontal Connector Position: Bottom #} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-connector', 'top', 'auto') }} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-connector', 'bottom', '10px') }} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-marker', 'top', 'auto') }} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-marker', 'bottom', '-55px') }} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-slider-container', 'padding-top', '0px') }} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-slider-container', 'padding-bottom', '60px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-nav-prev', 'left', '-13px') }} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-nav-prev', 'bottom', '-5px') }} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-nav-prev', 'top', 'unset') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-nav-next', 'right', '-13px') }} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-nav-next', 'bottom', '-5px') }} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-nav-next', 'top', 'unset') }} @@ -344,7 +372,8 @@ {# Content alignment based on connector position #} {{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-content', 'justify-content', 'flex-start') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-content', 'justify-content', 'flex-end') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd) .qx-timeline-content', 'justify-content', 'flex-end') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even) .qx-timeline-content', 'justify-content', 'flex-start') }} {# Timeline Date #} {{ style.css(id ~ ' .qx-timeline-date', 'color', dateColor) }} From 3981ffdbf174e57c48f1412dcfa44fe5afe21742 Mon Sep 17 00:00:00 2001 From: SagorIslamOfficial Date: Mon, 29 Sep 2025 17:28:42 +0600 Subject: [PATCH 13/17] feat: add content vertical alignment settings for horizontal layout --- timeline/config.yml | 20 ++++++++++++++++++++ timeline/partials/html.twig | 5 +++++ timeline/partials/style.twig | 26 +++++++++++++++++++++++--- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/timeline/config.yml b/timeline/config.yml index ef38fa2..cb5b4cf 100644 --- a/timeline/config.yml +++ b/timeline/config.yml @@ -161,6 +161,26 @@ form: hover: Show on Hover disabled: Disabled + - name: content_vertical_alignment + label: Content Vertical Alignment + type: choose + value: center + responsive: false + help: Vertical alignment of content within each timeline item + depends: + layout: horizontal + connector_position_horizontal: ["top", "bottom"] + options: + top: + label: Top + icon: qxuicon-arrow-to-top + center: + label: Center + icon: qxuicon-align-center + bottom: + label: Bottom + icon: qxuicon-arrow-to-bottom + styles: - name: timeline_appearance label: Timeline Appearance diff --git a/timeline/partials/html.twig b/timeline/partials/html.twig index 098954f..187a9d4 100644 --- a/timeline/partials/html.twig +++ b/timeline/partials/html.twig @@ -7,14 +7,19 @@ {% set connectorStyle = general.layout_settings.connector_style %} {% set connectorPosition = general.layout_settings.connector_position %} {% set connectorPositionHorizontal = general.layout_settings.connector_position_horizontal %} +{% set contentAlignment = general.layout_settings.content_vertical_alignment %} {# Use appropriate connector position based on layout #} {% set finalConnectorPosition = layout == 'horizontal' ? connectorPositionHorizontal : connectorPosition %} +{# Content alignment class for horizontal layout #} +{% set contentAlignmentClass = (layout == 'horizontal' and contentAlignment) ? 'qx-timeline-content-align-' ~ contentAlignment : '' %} + {% set classes = classNames('qx-element qx-element-timeline-v2', 'qx-timeline-' ~ layout, 'qx-timeline-align-' ~ alignment, 'qx-timeline-connector-pos-' ~ finalConnectorPosition, + contentAlignmentClass, visibilityClass(visibility), class ) %} diff --git a/timeline/partials/style.twig b/timeline/partials/style.twig index 02bfd8f..d2a9ba2 100644 --- a/timeline/partials/style.twig +++ b/timeline/partials/style.twig @@ -370,10 +370,30 @@ {# Reverse content order only for bottom connector position #} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-content', 'flex-direction', 'column-reverse') }} -{# Content alignment based on connector position #} +{# Content vertical alignment for horizontal layout #} +{% set contentAlignment = general.layout_settings.content_vertical_alignment %} + +{# Always generate all alignment classes for real-time builder updates #} +{{ style.css(id ~ '.qx-timeline-content-align-top.qx-timeline-horizontal .qx-timeline-content', 'justify-content', 'flex-start') }} +{{ style.css(id ~ '.qx-timeline-content-align-top .qx-timeline-horizontal .qx-timeline-content', 'justify-content', 'flex-start') }} + +{# Center alignment #} +{{ style.css(id ~ '.qx-timeline-content-align-center.qx-timeline-horizontal .qx-timeline-content', 'justify-content', 'center') }} +{{ style.css(id ~ '.qx-timeline-content-align-center .qx-timeline-horizontal .qx-timeline-content', 'justify-content', 'center') }} + +{# Bottom alignment #} +{{ style.css(id ~ '.qx-timeline-content-align-bottom.qx-timeline-horizontal .qx-timeline-content', 'justify-content', 'flex-end') }} +{{ style.css(id ~ '.qx-timeline-content-align-bottom .qx-timeline-horizontal .qx-timeline-content', 'justify-content', 'flex-end') }} + +{# Default content alignment based on connector position when no specific alignment is set #} {{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-content', 'justify-content', 'flex-start') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd) .qx-timeline-content', 'justify-content', 'flex-end') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even) .qx-timeline-content', 'justify-content', 'flex-start') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd) .qx-timeline-content', 'justify-content', 'center') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even) .qx-timeline-content', 'justify-content', 'center') }} + +{# Content alignment overrides #} +{{ style.css(id ~ '.qx-timeline-content-align-top .qx-timeline-horizontal .qx-timeline-item .qx-timeline-content', 'justify-content', 'flex-start') }} +{{ style.css(id ~ '.qx-timeline-content-align-center .qx-timeline-horizontal .qx-timeline-item .qx-timeline-content', 'justify-content', 'center') }} +{{ style.css(id ~ '.qx-timeline-content-align-bottom .qx-timeline-horizontal .qx-timeline-item .qx-timeline-content', 'justify-content', 'flex-end') }} {# Timeline Date #} {{ style.css(id ~ ' .qx-timeline-date', 'color', dateColor) }} From 431f6535def71b18a004fe5a6573f907647eef28 Mon Sep 17 00:00:00 2001 From: SagorIslamOfficial Date: Mon, 29 Sep 2025 19:27:55 +0600 Subject: [PATCH 14/17] feat: add options for showing markers and arrows --- timeline/config.yml | 12 +++++ timeline/partials/html.twig | 24 +++++++--- timeline/partials/style.twig | 89 +++++++++++++++++++++++++++++++++--- 3 files changed, 113 insertions(+), 12 deletions(-) diff --git a/timeline/config.yml b/timeline/config.yml index cb5b4cf..794e99e 100644 --- a/timeline/config.yml +++ b/timeline/config.yml @@ -76,6 +76,18 @@ form: value: true help: Display the connecting line between timeline items + - name: show_markers + label: Show Timeline Markers + type: switch + value: true + help: Display the marker dots/icons on timeline items + + - name: show_arrows + label: Show Arrow Indicators + type: switch + value: false + help: Display arrow indicators connecting timeline items to the main line + - name: connector_style label: Connector Style type: select diff --git a/timeline/partials/html.twig b/timeline/partials/html.twig index 187a9d4..26cd59d 100644 --- a/timeline/partials/html.twig +++ b/timeline/partials/html.twig @@ -8,6 +8,8 @@ {% set connectorPosition = general.layout_settings.connector_position %} {% set connectorPositionHorizontal = general.layout_settings.connector_position_horizontal %} {% set contentAlignment = general.layout_settings.content_vertical_alignment %} +{% set showMarkers = general.layout_settings.show_markers is defined ? general.layout_settings.show_markers : true %} +{% set showArrows = general.layout_settings.show_arrows is defined ? general.layout_settings.show_arrows : false %} {# Use appropriate connector position based on layout #} {% set finalConnectorPosition = layout == 'horizontal' ? connectorPositionHorizontal : connectorPosition %} @@ -47,9 +49,14 @@ {% endif %} {% for item in timelineItems %}
-
-
-
+ {% if showMarkers %} +
+
+
+ {% endif %} + {% if showArrows %} +
+ {% endif %}
@@ -115,9 +122,14 @@
{% for item in timelineItems %}
-
-
-
+ {% if showMarkers %} +
+
+
+ {% endif %} + {% if showArrows %} +
+ {% endif %}
{% if item.date %} diff --git a/timeline/partials/style.twig b/timeline/partials/style.twig index d2a9ba2..4b0b8ad 100644 --- a/timeline/partials/style.twig +++ b/timeline/partials/style.twig @@ -33,6 +33,8 @@ {% set hoverDescriptionColor = styles.hover_effects.hover_description_color %} {% set hoverDescriptionFont = styles.hover_effects.hover_description_font %} +{% set showMarkers = general.layout_settings.show_markers is defined ? general.layout_settings.show_markers : true %} +{% set showArrows = general.layout_settings.show_arrows is defined ? general.layout_settings.show_arrows : false %} {% set mobileLayout = styles.responsive_settings.mobile_layout %} {% set hideOnMobile = styles.responsive_settings.hide_on_mobile %} @@ -218,6 +220,7 @@ {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-item', 'flex-direction', 'column') }} {# Timeline Marker #} +{% if showMarkers %} {{ style.css(id ~ ' .qx-timeline-marker', 'position', 'absolute') }} {{ style.css(id ~ ' .qx-timeline-marker', 'display', 'flex') }} {{ style.css(id ~ ' .qx-timeline-marker', 'align-items', 'center') }} @@ -226,18 +229,30 @@ {{ style.css(id ~ ' .qx-timeline-marker', 'width', '15px') }} {{ style.css(id ~ ' .qx-timeline-marker', 'height', '15px') }} {{ style.css(id ~ ' .qx-timeline-marker', 'top', '5px') }} +{% else %} +{{ style.css(id ~ ' .qx-timeline-marker', 'display', 'none') }} +{% endif %} {# Vertical Marker Position #} +{% if showMarkers %} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'transform', 'translateX(-50%)') }} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'top', '0') }} +{% endif %} +{# Vertical Marker Position #} +{% if showMarkers %} {{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'transform', 'translateX(-50%)') }} {{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-marker', 'top', '0') }} +{% endif %} {# Connector Position: Right Side #} {{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical', 'padding-right', '0') }} {{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical', 'padding-left', '0') }} {{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical .qx-timeline-connector', 'left', 'auto') }} {{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical .qx-timeline-connector', 'right', '-0.1px') }} +{% if showMarkers %} {{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical .qx-timeline-marker', 'right', '-14px') }} +{% endif %} {# Center Connector Position: Alternate items left/right #} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical', 'padding-left', '0') }} @@ -257,18 +272,20 @@ {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical::after', 'clear', 'both') }} {# Center marker placement #} +{% if showMarkers %} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'left', 'auto') }} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'right', '-7.5px') }} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'transform', 'none') }} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-marker', 'left', '-7.5px') }} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-marker', 'transform', 'none') }} +{% endif %} {# Center content alignment #} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-content', 'text-align', 'right') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-content', 'margin-right', '25px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-content', 'margin-right', '45px') }} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-content', 'margin-left', '0') }} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-content', 'text-align', 'left') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-content', 'margin-left', '25px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-content', 'margin-left', '45px') }} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-content', 'margin-right', '0') }} {# Center image alignment #} @@ -277,9 +294,56 @@ {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-image', 'justify-content', 'flex-start') }} {# Horizontal Marker Position #} +{% if showMarkers %} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'top', '-52px') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'left', '50%') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-marker', 'transform', 'translateX(-50%)') }} +{% endif %} + +{# Arrow Indicators #} +{% if showArrows %} +{# Base arrow styles #} +{{ style.css(id ~ ' .qx-timeline-arrow', 'position', 'absolute') }} +{{ style.css(id ~ ' .qx-timeline-arrow', 'background-color', '#007cba') }} +{{ style.css(id ~ ' .qx-timeline-arrow', 'pointer-events', 'none') }} + +{# Vertical layout arrows - extending from dots toward content #} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-arrow', 'width', '30px') }} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-arrow', 'height', '2px') }} +{{ style.css(id ~ ' .qx-timeline-vertical .qx-timeline-arrow', 'top', '12px') }} + +{# Vertical left position arrows - extend from dot to the right toward content #} +{{ style.css(id ~ '.qx-timeline-connector-pos-left .qx-timeline-vertical .qx-timeline-arrow', 'left', '0') }} + +{# Vertical right position arrows - extend from dot to the left toward content #} +{{ style.css(id ~ '.qx-timeline-connector-pos-right .qx-timeline-vertical .qx-timeline-arrow', 'right', '0.01px') }} + +{# Vertical center position arrows - extend from dots toward content #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(odd) .qx-timeline-arrow', 'left', '0.001px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item:nth-child(even) .qx-timeline-arrow', 'right', '0.001px') }} + +{# Horizontal layout arrows - extending from dots toward content #} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-arrow', 'width', '2px') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-arrow', 'height', '30px') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-arrow', 'left', '50%') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-arrow', 'transform', 'translateX(-50%)') }} +{{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-arrow', 'z-index', '-10') }} + +{# Horizontal top position arrows - extend from dot downward toward content #} +{{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-arrow', 'top', '-42px') }} + +{# Horizontal bottom position arrows - extend from dot upward toward content #} +{{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-arrow', 'bottom', '-42px') }} + +{# Horizontal center position arrows - extend from dots toward content #} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd) .qx-timeline-arrow', 'bottom', '-42px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even) .qx-timeline-arrow', 'top', '-42px') }} + +{# Builder mode center position arrow adjustments #} +{% if mode == 'builder' %} + +{% endif %} +{% endif %} {# Horizontal Connector Position: Center #} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-connector', 'top', '50%') }} @@ -298,23 +362,31 @@ {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd)', 'transform', 'translateY(-170px)') }} {# Even items: below center line #} +{% if mode == 'builder' %} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even)', 'transform', 'translateY(180px)') }} +{% else %} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even)', 'transform', 'translateY(170px)') }} +{% endif %} {# Markers positioned ON the connector line #} +{% if showMarkers %} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd) .qx-timeline-marker', 'top', 'auto') }} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd) .qx-timeline-marker', 'bottom', '-50px') }} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'top', '-50px') }} +{% endif %} -{% if mode == 'builder' %} +{% if mode == 'builder' and showMarkers %} {# Builder mode center position marker fixes - these override the general styles above #} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd)', 'transform', 'translateY(-150px)') }} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(odd) .qx-timeline-marker', 'bottom', '-42px') }} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'top', '-38px') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even) .qx-timeline-marker', 'top', '-45px') }} {% endif %} {# Horizontal Connector Position: Top #} {{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-connector', 'top', '12px') }} +{% if showMarkers %} {{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-marker', 'top', '-50px') }} +{% endif %} {# Horizontal Top Position Navigation #} {{ style.css(id ~ '.qx-timeline-connector-pos-top .qx-timeline-horizontal .qx-timeline-nav-prev', 'left', '-13px') }} @@ -335,8 +407,10 @@ {# Horizontal Connector Position: Bottom #} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-connector', 'top', 'auto') }} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-connector', 'bottom', '10px') }} +{% if showMarkers %} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-marker', 'top', 'auto') }} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-marker', 'bottom', '-55px') }} +{% endif %} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-slider-container', 'padding-top', '0px') }} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-slider-container', 'padding-bottom', '60px') }} {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-nav-prev', 'left', '-13px') }} @@ -347,10 +421,12 @@ {{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-nav-next', 'top', 'unset') }} {# Timeline Dot (default marker) #} +{% if showMarkers %} {{ style.css(id ~ ' .qx-timeline-dot', 'width', '15px') }} {{ style.css(id ~ ' .qx-timeline-dot', 'height', '15px') }} {{ style.css(id ~ ' .qx-timeline-dot', 'border-radius', '50%') }} {{ style.css(id ~ ' .qx-timeline-dot', 'background-color', connectorColor) }} +{% endif %} {# Icon Styling #} @@ -367,8 +443,7 @@ {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-content', 'display', 'flex') }} {{ style.css(id ~ ' .qx-timeline-horizontal .qx-timeline-content', 'flex-direction', 'column') }} -{# Reverse content order only for bottom connector position #} -{{ style.css(id ~ '.qx-timeline-connector-pos-bottom .qx-timeline-horizontal .qx-timeline-content', 'flex-direction', 'column-reverse') }} + {# Content vertical alignment for horizontal layout #} {% set contentAlignment = general.layout_settings.content_vertical_alignment %} @@ -424,12 +499,14 @@ {{ style.css(id ~ ' .qx-timeline-description p:last-child', 'margin-bottom', '0') }} {# Active Item Styling #} +{% if showMarkers %} {{ style.css(id ~ ' .qx-timeline-item-active .qx-timeline-marker', 'transform', 'translateX(-50%) scale(1.4)') }} {{ style.css(id ~ ' .qx-timeline-item-active .qx-timeline-dot', 'background-color', connectorColor) }} {# Active Item Styling for Center Position #} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item-active:nth-child(even) .qx-timeline-marker', 'transform', 'scale(1.4)') }} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-vertical .qx-timeline-item-active:nth-child(odd) .qx-timeline-marker', 'transform', 'scale(1.4)') }} +{% endif %} {# Hover Effects #} {% if enableHover %} From 5d79d04941199fcd716e694097b1b4d9cb8449a4 Mon Sep 17 00:00:00 2001 From: SagorIslamOfficial Date: Tue, 30 Sep 2025 11:29:17 +0600 Subject: [PATCH 15/17] feat: update arrow background color to use connector color variable --- timeline/partials/style.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/timeline/partials/style.twig b/timeline/partials/style.twig index 4b0b8ad..501f2d6 100644 --- a/timeline/partials/style.twig +++ b/timeline/partials/style.twig @@ -304,7 +304,7 @@ {% if showArrows %} {# Base arrow styles #} {{ style.css(id ~ ' .qx-timeline-arrow', 'position', 'absolute') }} -{{ style.css(id ~ ' .qx-timeline-arrow', 'background-color', '#007cba') }} +{{ style.css(id ~ ' .qx-timeline-arrow', 'background-color', connectorColor) }} {{ style.css(id ~ ' .qx-timeline-arrow', 'pointer-events', 'none') }} {# Vertical layout arrows - extending from dots toward content #} From 3b073fb11e2455f7dc3686a8d4075cb523f149d7 Mon Sep 17 00:00:00 2001 From: SagorIslamOfficial Date: Tue, 30 Sep 2025 12:19:25 +0600 Subject: [PATCH 16/17] fix: adjust vertical positioning of even items --- timeline/partials/style.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/timeline/partials/style.twig b/timeline/partials/style.twig index 501f2d6..e32fd04 100644 --- a/timeline/partials/style.twig +++ b/timeline/partials/style.twig @@ -363,7 +363,7 @@ {# Even items: below center line #} {% if mode == 'builder' %} -{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even)', 'transform', 'translateY(180px)') }} +{{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even)', 'transform', 'translateY(177px)') }} {% else %} {{ style.css(id ~ '.qx-timeline-connector-pos-center .qx-timeline-horizontal .qx-timeline-item:nth-child(even)', 'transform', 'translateY(170px)') }} {% endif %} From d618b53dcc3dc626974eca1d00fe3d9ac6aac401 Mon Sep 17 00:00:00 2001 From: SagorIslamOfficial Date: Tue, 30 Sep 2025 17:48:46 +0600 Subject: [PATCH 17/17] feat: add 'Slide As A Set' option and enhance autoplay functionality --- timeline/config.yml | 8 ++++++++ timeline/partials/html.twig | 19 +++++++++++++++++-- timeline/partials/script.twig | 29 ++++++++++++++++++++++++++--- timeline/partials/style.twig | 2 +- 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/timeline/config.yml b/timeline/config.yml index 794e99e..60be59d 100644 --- a/timeline/config.yml +++ b/timeline/config.yml @@ -173,6 +173,14 @@ form: hover: Show on Hover disabled: Disabled + - name: slide_as_set + label: Slide As A Set + type: switch + value: false + help: Navigate by full sets of items instead of one item at a time + depends: + layout: horizontal + - name: content_vertical_alignment label: Content Vertical Alignment type: choose diff --git a/timeline/partials/html.twig b/timeline/partials/html.twig index 26cd59d..ce489ff 100644 --- a/timeline/partials/html.twig +++ b/timeline/partials/html.twig @@ -98,11 +98,26 @@ {% set itemsPerView = general.layout_settings.items_per_view %} {% endif %} {% endif %} + {% set autoPlaySpeed = general.layout_settings.auto_play_speed %} + {% if autoPlaySpeed.desktop is defined %} + {% set autoPlaySpeed = autoPlaySpeed.desktop %} + {% elseif autoPlaySpeed.value is defined %} + {% set autoPlaySpeed = autoPlaySpeed.value %} + {% elseif autoPlaySpeed is iterable %} + {% set autoPlaySpeed = autoPlaySpeed|first %} + {% endif %} + + {% if autoPlaySpeed > 100 %} + {% set autoPlaySpeed = (autoPlaySpeed / 1000)|round %} + {% endif %} +
+ data-auto-speed="{{ autoPlaySpeed ?: 3 }}" + data-show-nav="{{ general.layout_settings.show_navigation ?: 'hover' }}" + data-slide-as-set="{{ general.layout_settings.slide_as_set ? 'true' : 'false' }}" + data-debug-slide-as-set="{{ general.layout_settings.slide_as_set }}">
{% if showConnector %} diff --git a/timeline/partials/script.twig b/timeline/partials/script.twig index d1f6671..d1af6c6 100644 --- a/timeline/partials/script.twig +++ b/timeline/partials/script.twig @@ -83,9 +83,23 @@ jQuery(function($) { var itemsPerView = parseInt($horizontalTimeline.data('items-per-view')) || 3; var sliderMode = $horizontalTimeline.data('slider-mode') || 'manual'; - var autoSpeed = parseInt($horizontalTimeline.data('auto-speed')) || 3; + + var slideAsSetAttr = $horizontalTimeline.data('slide-as-set'); + var debugSlideAsSetAttr = $horizontalTimeline.data('debug-slide-as-set'); + var slideAsSet = slideAsSetAttr === true || slideAsSetAttr === 'true'; + + var autoSpeed = 3; + try { + var speedAttr = $horizontalTimeline[0].getAttribute('data-auto-speed'); + if (speedAttr && speedAttr !== '[object Object]' && !isNaN(parseInt(speedAttr))) { + autoSpeed = parseInt(speedAttr); + } + } catch (e) { + autoSpeed = 3; + } + var currentSlide = 0; - var totalSlides = Math.ceil($items.length / itemsPerView); + var totalSlides = slideAsSet ? Math.ceil($items.length / itemsPerView) : Math.max(1, $items.length - itemsPerView + 1); var autoPlayInterval; function updateSliderWidth() { @@ -113,7 +127,14 @@ jQuery(function($) { var containerWidth = $horizontalTimeline.find('.qx-timeline-slider-container').width(); var spacing = 25; var itemWidth = (containerWidth - (itemsPerView - 1) * spacing) / itemsPerView; - var moveDistance = slideIndex * (itemWidth + spacing) * itemsPerView; + + var moveDistance; + if (slideAsSet) { + moveDistance = slideIndex * (itemWidth + spacing) * itemsPerView; + } else { + moveDistance = slideIndex * (itemWidth + spacing); + } + $track.css('transform', 'translateX(-' + moveDistance + 'px)'); $prevBtn.toggleClass('disabled', slideIndex === 0); @@ -130,6 +151,7 @@ jQuery(function($) { function startAutoPlay() { if (sliderMode === 'auto') { + stopAutoPlay(); autoPlayInterval = setInterval(function() { nextSlide(); }, autoSpeed * 1000); @@ -139,6 +161,7 @@ jQuery(function($) { function stopAutoPlay() { if (autoPlayInterval) { clearInterval(autoPlayInterval); + autoPlayInterval = null; } } diff --git a/timeline/partials/style.twig b/timeline/partials/style.twig index e32fd04..97f628d 100644 --- a/timeline/partials/style.twig +++ b/timeline/partials/style.twig @@ -126,7 +126,7 @@ {# Horizontal Slider Track #} {{ style.css(id ~ ' .qx-timeline-slider-track', 'display', 'flex') }} -{{ style.css(id ~ ' .qx-timeline-slider-track', 'transition', 'transform 0.3s ease') }} +{{ style.css(id ~ ' .qx-timeline-slider-track', 'transition', 'transform 0.6s ease-out') }} {{ style.css(id ~ ' .qx-timeline-slider-track', 'align-items', 'stretch') }} {{ style.css(id ~ ' .qx-timeline-slider-track', 'width', 'auto') }} {% if mode != 'builder' %}