From 7063b6d6330b1257ea948b386ea8ca496fd26daf Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 20 Jun 2026 20:22:47 -0500 Subject: [PATCH] feat: add collapsible floating social bar, tooltips, and URL validation - Floating social bar now has a toggle button to collapse/expand, with state persisted in localStorage - All social icon links show Bootstrap tooltips on hover with the platform name, placement-aware based on position - Enhanced URL validation: server-side Joomla validate="url" on all social URL fields, stricter PHP regex with Joomla log warnings for skipped invalid URLs - Updated CSS Variables tab docs for floating bar variables --- CHANGELOG.md | 5 ++ source/html/layouts/mokoonyx/social-icons.php | 40 ++++++++-- source/language/en-GB/tpl_mokoonyx.ini | 3 +- source/language/en-US/tpl_mokoonyx.ini | 3 +- source/media/css/template.css | 76 ++++++++++++++++--- source/media/js/template.js | 42 ++++++++++ source/templateDetails.xml | 44 +++++------ 7 files changed, 172 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf294c8..b38b582 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,11 @@ ## [Unreleased] +### Added +- Collapsible floating social bar with toggle button and localStorage persistence +- Bootstrap tooltips on all social icon links showing platform name on hover +- URL validation on social platform fields (browser, Joomla server-side, and PHP layout logging) + ### Fixed - Moved footer social icons below footer-menu and footer module positions for correct display order diff --git a/source/html/layouts/mokoonyx/social-icons.php b/source/html/layouts/mokoonyx/social-icons.php index 7ba30d5..c575040 100644 --- a/source/html/layouts/mokoonyx/social-icons.php +++ b/source/html/layouts/mokoonyx/social-icons.php @@ -52,13 +52,22 @@ $platforms = [ $active = []; foreach ($platforms as $key => [$iconClass, $langKey]) { $url = trim((string) $params->get('social_' . $key . '_url', '')); - if ($url !== '' && preg_match('#^(https?://|mailto:|/)#i', $url)) { - $active[] = [ - 'url' => $url, - 'iconClass' => $iconClass, - 'label' => Text::_($langKey), - ]; + if ($url === '') { + continue; } + if (!preg_match('#^(https?://[^\s<>"]+|mailto:[^\s<>"]+|/[^\s<>"]*)$#i', $url)) { + \Joomla\CMS\Log\Log::add( + 'MokoOnyx social: skipped invalid URL for "' . $key . '": ' . $url, + \Joomla\CMS\Log\Log::WARNING, + 'template' + ); + continue; + } + $active[] = [ + 'url' => $url, + 'iconClass' => $iconClass, + 'label' => Text::_($langKey), + ]; } if (empty($active)) { @@ -84,6 +93,10 @@ if ($position === 'floating') { $listClass .= ' moko-social-icons--floating-' . $floatingPos; } ?> + +
+ + + +
+ diff --git a/source/language/en-GB/tpl_mokoonyx.ini b/source/language/en-GB/tpl_mokoonyx.ini index 25e16e9..566a177 100644 --- a/source/language/en-GB/tpl_mokoonyx.ini +++ b/source/language/en-GB/tpl_mokoonyx.ini @@ -286,6 +286,7 @@ TPL_MOKOONYX_SOCIAL_FLOATING_POS_LABEL="Floating Position" TPL_MOKOONYX_SOCIAL_FLOATING_POS_DESC="Which side of the screen the floating social bar appears on." TPL_MOKOONYX_SOCIAL_FLOATING_POS_LEFT="Left" TPL_MOKOONYX_SOCIAL_FLOATING_POS_RIGHT="Right" +TPL_MOKOONYX_SOCIAL_FLOATING_TOGGLE="Toggle social icons sidebar" TPL_MOKOONYX_SOCIAL_COLOR_LABEL="Icon Colour" TPL_MOKOONYX_SOCIAL_COLOR_DESC="Choose the colour scheme for social icons." TPL_MOKOONYX_SOCIAL_COLOR_THEME="Theme (CSS variables)" @@ -321,7 +322,7 @@ TPL_MOKOONYX_SOCIAL_MAIL="Email us" ; ===== CSS Variables tab (social) ===== TPL_MOKOONYX_CSS_VARS_SOCIAL_LABEL="Social Icons" -TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="--social-icon-size — Icon font size (overrides size preset)
--social-icon-gap — Gap between icons (default: 0.5rem)
--social-icon-color — Icon colour (default: currentColor)
--social-icon-hover-color — Hover colour (default: var(--accent-color-primary))
--social-icon-bg — Background for circle/rounded styles
--social-icon-hover-bg — Hover background
--social-icon-radius — Border radius for rounded style (default: 0.375rem)" +TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="--social-icon-size — Icon font size (overrides size preset)
--social-icon-gap — Gap between icons (default: 0.5rem)
--social-icon-color — Icon colour (default: currentColor)
--social-icon-hover-color — Hover colour (default: var(--accent-color-primary))
--social-icon-bg — Background for circle/rounded styles
--social-icon-hover-bg — Hover background
--social-icon-radius — Border radius for rounded style (default: 0.375rem)

Floating bar
The collapse toggle button inherits --social-icon-bg, --social-icon-color, and --social-icon-hover-bg. Tooltips appear automatically on hover showing the platform name." ; ===== Misc ===== MOD_BREADCRUMBS_HERE="YOU ARE HERE:" diff --git a/source/language/en-US/tpl_mokoonyx.ini b/source/language/en-US/tpl_mokoonyx.ini index 3e60211..a671025 100644 --- a/source/language/en-US/tpl_mokoonyx.ini +++ b/source/language/en-US/tpl_mokoonyx.ini @@ -286,6 +286,7 @@ TPL_MOKOONYX_SOCIAL_FLOATING_POS_LABEL="Floating Position" TPL_MOKOONYX_SOCIAL_FLOATING_POS_DESC="Which side of the screen the floating social bar appears on." TPL_MOKOONYX_SOCIAL_FLOATING_POS_LEFT="Left" TPL_MOKOONYX_SOCIAL_FLOATING_POS_RIGHT="Right" +TPL_MOKOONYX_SOCIAL_FLOATING_TOGGLE="Toggle social icons sidebar" TPL_MOKOONYX_SOCIAL_COLOR_LABEL="Icon Color" TPL_MOKOONYX_SOCIAL_COLOR_DESC="Choose the color scheme for social icons." TPL_MOKOONYX_SOCIAL_COLOR_THEME="Theme (CSS variables)" @@ -321,7 +322,7 @@ TPL_MOKOONYX_SOCIAL_MAIL="Email us" ; ===== CSS Variables tab (social) ===== TPL_MOKOONYX_CSS_VARS_SOCIAL_LABEL="Social Icons" -TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="--social-icon-size — Icon font size (overrides size preset)
--social-icon-gap — Gap between icons (default: 0.5rem)
--social-icon-color — Icon color (default: currentColor)
--social-icon-hover-color — Hover color (default: var(--accent-color-primary))
--social-icon-bg — Background for circle/rounded styles
--social-icon-hover-bg — Hover background
--social-icon-radius — Border radius for rounded style (default: 0.375rem)" +TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="--social-icon-size — Icon font size (overrides size preset)
--social-icon-gap — Gap between icons (default: 0.5rem)
--social-icon-color — Icon color (default: currentColor)
--social-icon-hover-color — Hover color (default: var(--accent-color-primary))
--social-icon-bg — Background for circle/rounded styles
--social-icon-hover-bg — Hover background
--social-icon-radius — Border radius for rounded style (default: 0.375rem)

Floating bar
The collapse toggle button inherits --social-icon-bg, --social-icon-color, and --social-icon-hover-bg. Tooltips appear automatically on hover showing the platform name." ; ===== Misc ===== MOD_BREADCRUMBS_HERE="YOU ARE HERE:" diff --git a/source/media/css/template.css b/source/media/css/template.css index 9b4762a..4924520 100644 --- a/source/media/css/template.css +++ b/source/media/css/template.css @@ -23679,26 +23679,42 @@ font-size: 0.8125rem; } /* Position: floating — fixed sidebar, hidden on mobile */ -.moko-social-icons--floating { +.moko-social-floating-wrap { position: fixed; top: 50%; transform: translateY(-50%); z-index: 1030; display: flex; + align-items: center; + transition: transform 0.3s ease; +} + +.moko-social-floating-wrap--left { + left: 0; + flex-direction: row; +} + +.moko-social-floating-wrap--right { + right: 0; + flex-direction: row-reverse; +} + +.moko-social-floating-wrap--left.is-collapsed { + transform: translateY(-50%) translateX(calc(-100% + 1.5rem)); +} + +.moko-social-floating-wrap--right.is-collapsed { + transform: translateY(-50%) translateX(calc(100% - 1.5rem)); +} + +.moko-social-icons--floating { + display: flex; } .moko-social-icons--floating ul { flex-direction: column; } -.moko-social-icons--floating-left { - left: 0; -} - -.moko-social-icons--floating-right { - right: 0; -} - .moko-social-icons--floating-left a { border-radius: 0 var(--social-icon-radius, 0.375rem) var(--social-icon-radius, 0.375rem) 0; } @@ -23712,8 +23728,48 @@ font-size: 0.8125rem; border-radius: inherit; } +/* Floating toggle button */ +.moko-social-floating-toggle { + display: flex; + align-items: center; + justify-content: center; + width: 1.5rem; + height: 2.5rem; + padding: 0; + border: 1px solid var(--social-icon-bg, var(--border-color, hsl(0, 0%, 80%))); + background: var(--social-icon-bg, var(--body-bg, hsl(0, 0%, 100%))); + color: var(--social-icon-color, currentColor); + cursor: pointer; + font-size: 0.65rem; + transition: background 0.2s, color 0.2s; + flex-shrink: 0; +} + +.moko-social-floating-wrap--left .moko-social-floating-toggle { + border-radius: 0 var(--social-icon-radius, 0.375rem) var(--social-icon-radius, 0.375rem) 0; + border-left: 0; +} + +.moko-social-floating-wrap--right .moko-social-floating-toggle { + border-radius: var(--social-icon-radius, 0.375rem) 0 0 var(--social-icon-radius, 0.375rem); + border-right: 0; +} + +.moko-social-floating-toggle:hover { + background: var(--social-icon-hover-bg, var(--accent-color-primary, hsl(220, 70%, 50%))); + color: #fff; +} + +.moko-social-floating-toggle .fa-solid { + transition: transform 0.3s ease; +} + +.is-collapsed .moko-social-floating-toggle .fa-solid { + transform: rotate(180deg); +} + @media (max-width: 991.98px) { - .moko-social-icons--floating { + .moko-social-floating-wrap { display: none; } } diff --git a/source/media/js/template.js b/source/media/js/template.js index 70a2f6a..b650658 100644 --- a/source/media/js/template.js +++ b/source/media/js/template.js @@ -789,6 +789,46 @@ }); } + // ======================================================================== + // FLOATING SOCIAL BAR (collapsible) + // ======================================================================== + var socialStorageKey = "moko-social-collapsed"; + + function initSocialFloating() { + var wrap = doc.getElementById("mokoSocialFloating"); + var toggle = doc.getElementById("mokoSocialFloatingToggle"); + if (!wrap || !toggle) return; + + // Restore saved state + try { + if (localStorage.getItem(socialStorageKey) === "1") { + wrap.classList.add("is-collapsed"); + toggle.setAttribute("aria-expanded", "false"); + } + } catch (e) {} + + toggle.addEventListener("click", function () { + var collapsed = wrap.classList.toggle("is-collapsed"); + toggle.setAttribute("aria-expanded", collapsed ? "false" : "true"); + try { localStorage.setItem(socialStorageKey, collapsed ? "1" : "0"); } catch (e) {} + }); + } + + // ======================================================================== + // SOCIAL ICON TOOLTIPS + // ======================================================================== + function initSocialTooltips() { + var triggers = doc.querySelectorAll('.moko-social-icons [data-bs-toggle="tooltip"]'); + if (!triggers.length) return; + + // Bootstrap 5 tooltip init + if (typeof bootstrap !== "undefined" && bootstrap.Tooltip) { + triggers.forEach(function (el) { + new bootstrap.Tooltip(el, { trigger: "hover focus" }); + }); + } + } + /** * Run all template JS initializations */ @@ -815,6 +855,8 @@ initBackTop(); initSearchToggle(); initSidebarAccordion(); + initSocialFloating(); + initSocialTooltips(); initVarCopy(); } diff --git a/source/templateDetails.xml b/source/templateDetails.xml index 441f3c9..bfdb28c 100644 --- a/source/templateDetails.xml +++ b/source/templateDetails.xml @@ -387,28 +387,28 @@ showon="social_topbar:1[OR]social_footer:1[OR]social_floating:1" /> - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + -- 2.52.0