feat: collapsible floating social bar, tooltips, and URL validation #141
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
?>
|
||||
<?php if ($position === 'floating') : ?>
|
||||
<div class="moko-social-floating-wrap moko-social-floating-wrap--<?php echo htmlspecialchars($floatingPos, ENT_QUOTES, 'UTF-8'); ?>"
|
||||
id="mokoSocialFloating">
|
||||
<?php endif; ?>
|
||||
<nav class="<?php echo $listClass; ?>" aria-label="<?php echo Text::_('TPL_MOKOONYX_SOCIAL_NAV_LABEL'); ?>">
|
||||
<ul>
|
||||
<?php foreach ($active as $item) : ?>
|
||||
@@ -91,10 +104,23 @@ if ($position === 'floating') {
|
||||
<a href="<?php echo htmlspecialchars($item['url'], ENT_QUOTES, 'UTF-8'); ?>"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="<?php echo htmlspecialchars($item['label'], ENT_QUOTES, 'UTF-8'); ?>">
|
||||
aria-label="<?php echo htmlspecialchars($item['label'], ENT_QUOTES, 'UTF-8'); ?>"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="<?php echo $position === 'floating' ? ($floatingPos === 'left' ? 'right' : 'left') : 'top'; ?>"
|
||||
title="<?php echo htmlspecialchars($item['label'], ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<span class="<?php echo htmlspecialchars($item['iconClass'], ENT_QUOTES, 'UTF-8'); ?>" aria-hidden="true"></span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</nav>
|
||||
<?php if ($position === 'floating') : ?>
|
||||
<button type="button"
|
||||
class="moko-social-floating-toggle"
|
||||
id="mokoSocialFloatingToggle"
|
||||
aria-label="<?php echo Text::_('TPL_MOKOONYX_SOCIAL_FLOATING_TOGGLE'); ?>"
|
||||
aria-expanded="true">
|
||||
<span class="fa-solid fa-chevron-<?php echo $floatingPos === 'left' ? 'left' : 'right'; ?>" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -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="<code>--social-icon-size</code> — Icon font size (overrides size preset)<br><code>--social-icon-gap</code> — Gap between icons (default: <code>0.5rem</code>)<br><code>--social-icon-color</code> — Icon colour (default: <code>currentColor</code>)<br><code>--social-icon-hover-color</code> — Hover colour (default: <code>var(--accent-color-primary)</code>)<br><code>--social-icon-bg</code> — Background for circle/rounded styles<br><code>--social-icon-hover-bg</code> — Hover background<br><code>--social-icon-radius</code> — Border radius for rounded style (default: <code>0.375rem</code>)"
|
||||
TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="<code>--social-icon-size</code> — Icon font size (overrides size preset)<br><code>--social-icon-gap</code> — Gap between icons (default: <code>0.5rem</code>)<br><code>--social-icon-color</code> — Icon colour (default: <code>currentColor</code>)<br><code>--social-icon-hover-color</code> — Hover colour (default: <code>var(--accent-color-primary)</code>)<br><code>--social-icon-bg</code> — Background for circle/rounded styles<br><code>--social-icon-hover-bg</code> — Hover background<br><code>--social-icon-radius</code> — Border radius for rounded style (default: <code>0.375rem</code>)<br><br><strong>Floating bar</strong><br>The collapse toggle button inherits <code>--social-icon-bg</code>, <code>--social-icon-color</code>, and <code>--social-icon-hover-bg</code>. Tooltips appear automatically on hover showing the platform name."
|
||||
|
||||
; ===== Misc =====
|
||||
MOD_BREADCRUMBS_HERE="YOU ARE HERE:"
|
||||
|
||||
@@ -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="<code>--social-icon-size</code> — Icon font size (overrides size preset)<br><code>--social-icon-gap</code> — Gap between icons (default: <code>0.5rem</code>)<br><code>--social-icon-color</code> — Icon color (default: <code>currentColor</code>)<br><code>--social-icon-hover-color</code> — Hover color (default: <code>var(--accent-color-primary)</code>)<br><code>--social-icon-bg</code> — Background for circle/rounded styles<br><code>--social-icon-hover-bg</code> — Hover background<br><code>--social-icon-radius</code> — Border radius for rounded style (default: <code>0.375rem</code>)"
|
||||
TPL_MOKOONYX_CSS_VARS_SOCIAL_DESC="<code>--social-icon-size</code> — Icon font size (overrides size preset)<br><code>--social-icon-gap</code> — Gap between icons (default: <code>0.5rem</code>)<br><code>--social-icon-color</code> — Icon color (default: <code>currentColor</code>)<br><code>--social-icon-hover-color</code> — Hover color (default: <code>var(--accent-color-primary)</code>)<br><code>--social-icon-bg</code> — Background for circle/rounded styles<br><code>--social-icon-hover-bg</code> — Hover background<br><code>--social-icon-radius</code> — Border radius for rounded style (default: <code>0.375rem</code>)<br><br><strong>Floating bar</strong><br>The collapse toggle button inherits <code>--social-icon-bg</code>, <code>--social-icon-color</code>, and <code>--social-icon-hover-bg</code>. Tooltips appear automatically on hover showing the platform name."
|
||||
|
||||
; ===== Misc =====
|
||||
MOD_BREADCRUMBS_HERE="YOU ARE HERE:"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+22
-22
@@ -387,28 +387,28 @@
|
||||
showon="social_topbar:1[OR]social_footer:1[OR]social_floating:1" />
|
||||
<field name="social_urls_note" type="note" description="TPL_MOKOONYX_SOCIAL_URLS_NOTE"
|
||||
showon="social_topbar:1[OR]social_footer:1[OR]social_floating:1" />
|
||||
<field name="social_facebook_url" type="url" default="" label="Facebook" filter="url" />
|
||||
<field name="social_twitter_url" type="url" default="" label="X / Twitter" filter="url" />
|
||||
<field name="social_instagram_url" type="url" default="" label="Instagram" filter="url" />
|
||||
<field name="social_linkedin_url" type="url" default="" label="LinkedIn" filter="url" />
|
||||
<field name="social_youtube_url" type="url" default="" label="YouTube" filter="url" />
|
||||
<field name="social_github_url" type="url" default="" label="GitHub" filter="url" />
|
||||
<field name="social_bluesky_url" type="url" default="" label="Bluesky" filter="url" />
|
||||
<field name="social_threads_url" type="url" default="" label="Threads" filter="url" />
|
||||
<field name="social_discord_url" type="url" default="" label="Discord" filter="url" />
|
||||
<field name="social_tiktok_url" type="url" default="" label="TikTok" filter="url" />
|
||||
<field name="social_reddit_url" type="url" default="" label="Reddit" filter="url" />
|
||||
<field name="social_pinterest_url" type="url" default="" label="Pinterest" filter="url" />
|
||||
<field name="social_snapchat_url" type="url" default="" label="Snapchat" filter="url" />
|
||||
<field name="social_telegram_url" type="url" default="" label="Telegram" filter="url" />
|
||||
<field name="social_whatsapp_url" type="url" default="" label="WhatsApp" filter="url" />
|
||||
<field name="social_tumblr_url" type="url" default="" label="Tumblr" filter="url" />
|
||||
<field name="social_twitch_url" type="url" default="" label="Twitch" filter="url" />
|
||||
<field name="social_spotify_url" type="url" default="" label="Spotify" filter="url" />
|
||||
<field name="social_soundcloud_url" type="url" default="" label="SoundCloud" filter="url" />
|
||||
<field name="social_flickr_url" type="url" default="" label="Flickr" filter="url" />
|
||||
<field name="social_vimeo_url" type="url" default="" label="Vimeo" filter="url" />
|
||||
<field name="social_linktree_url" type="url" default="" label="Linktree" filter="url" />
|
||||
<field name="social_facebook_url" type="url" default="" label="Facebook" filter="url" validate="url" />
|
||||
<field name="social_twitter_url" type="url" default="" label="X / Twitter" filter="url" validate="url" />
|
||||
<field name="social_instagram_url" type="url" default="" label="Instagram" filter="url" validate="url" />
|
||||
<field name="social_linkedin_url" type="url" default="" label="LinkedIn" filter="url" validate="url" />
|
||||
<field name="social_youtube_url" type="url" default="" label="YouTube" filter="url" validate="url" />
|
||||
<field name="social_github_url" type="url" default="" label="GitHub" filter="url" validate="url" />
|
||||
<field name="social_bluesky_url" type="url" default="" label="Bluesky" filter="url" validate="url" />
|
||||
<field name="social_threads_url" type="url" default="" label="Threads" filter="url" validate="url" />
|
||||
<field name="social_discord_url" type="url" default="" label="Discord" filter="url" validate="url" />
|
||||
<field name="social_tiktok_url" type="url" default="" label="TikTok" filter="url" validate="url" />
|
||||
<field name="social_reddit_url" type="url" default="" label="Reddit" filter="url" validate="url" />
|
||||
<field name="social_pinterest_url" type="url" default="" label="Pinterest" filter="url" validate="url" />
|
||||
<field name="social_snapchat_url" type="url" default="" label="Snapchat" filter="url" validate="url" />
|
||||
<field name="social_telegram_url" type="url" default="" label="Telegram" filter="url" validate="url" />
|
||||
<field name="social_whatsapp_url" type="url" default="" label="WhatsApp" filter="url" validate="url" />
|
||||
<field name="social_tumblr_url" type="url" default="" label="Tumblr" filter="url" validate="url" />
|
||||
<field name="social_twitch_url" type="url" default="" label="Twitch" filter="url" validate="url" />
|
||||
<field name="social_spotify_url" type="url" default="" label="Spotify" filter="url" validate="url" />
|
||||
<field name="social_soundcloud_url" type="url" default="" label="SoundCloud" filter="url" validate="url" />
|
||||
<field name="social_flickr_url" type="url" default="" label="Flickr" filter="url" validate="url" />
|
||||
<field name="social_vimeo_url" type="url" default="" label="Vimeo" filter="url" validate="url" />
|
||||
<field name="social_linktree_url" type="url" default="" label="Linktree" filter="url" validate="url" />
|
||||
<field name="social_mail_url" type="url" default="" label="Email" description="TPL_MOKOONYX_SOCIAL_MAIL_DESC" filter="string" />
|
||||
</fieldset>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user