From 3106856370d0aefb72b89c0c4b6f96794d002fe2 Mon Sep 17 00:00:00 2001 From: Aman kumar vishwakrma <186990161+amankv1234@users.noreply.github.com> Date: Wed, 5 Aug 2026 02:20:49 +0530 Subject: [PATCH 1/4] feat: implement QR code extension with dynamic CDN loading --- README.md | 19 +++- package.json | 1 + src/social-share-button-qr.js | 176 ++++++++++++++++++++++++++++++++++ src/social-share-button.css | 65 +++++++++++++ src/social-share-button.js | 6 ++ 5 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 src/social-share-button-qr.js diff --git a/README.md b/README.md index 7a7db56e..0bfc4fd7 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Lightweight social sharing component for web applications. Zero dependencies, fr ## Features -- 🌐 Multiple platforms: WhatsApp, Facebook, X, LinkedIn, Telegram, Reddit, Email, Pinterest, Discord +- 🌐 Multiple platforms: WhatsApp, Facebook, X, LinkedIn, Telegram, Reddit, Email, Pinterest, Discord, QR Code - 🎯 Zero dependencies - pure vanilla JavaScript - ⚛️ Framework support: React, Preact, Next.js, Qwik, Vue, Angular, or plain HTML - 🔄 Auto-detects current URL and page title @@ -527,7 +527,7 @@ new SocialShareButton({ | `onCopy` | function | `null` | Callback when user copies link: `(url) => {}` | **Available Platforms:** -`whatsapp`, `facebook`, `twitter`, `linkedin`, `telegram`, `reddit`, `email`, `pinterest`, `discord` +`whatsapp`, `facebook`, `twitter`, `linkedin`, `telegram`, `reddit`, `email`, `pinterest`, `discord`, `qrcode` ### Customize Share Message/Post Text @@ -659,6 +659,21 @@ new SocialShareButton({ ## Advanced Usage +### Using the QR Code Extension + +To enable the QR Code feature, you must include the extension script in your HTML along with the main script: + +```html + + + + + + +``` + +When you pass `qrcode` in the `platforms` array, a QR code generation panel will be rendered inline inside the share modal when clicked. + ### Using npm Package ```javascript diff --git a/package.json b/package.json index 972ad17e..3c2a76ce 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "src/social-share-button.js", "src/social-share-button.css", "src/social-share-button-react.jsx", + "src/social-share-button-qr.js", "src/social-share-analytics.js", "README.md", "LICENSE" diff --git a/src/social-share-button-qr.js b/src/social-share-button-qr.js new file mode 100644 index 00000000..06b7710d --- /dev/null +++ b/src/social-share-button-qr.js @@ -0,0 +1,176 @@ +/** + * SocialShareButton QR Code Extension + * Dynamically loads Kazuhiko Arase's qrcode-generator from CDN + */ + +(function () { + function applyQRPatch() { + if (typeof window === "undefined" || !window.SocialShareButton) { + console.warn("SocialShareButton core must be loaded before QR extension"); + return; + } + + // Guard against double-patching + if (window.SocialShareButton._qrPatched) return; + window.SocialShareButton._qrPatched = true; + + var originalShare = window.SocialShareButton.prototype.share; + var originalCloseModal = window.SocialShareButton.prototype.closeModal; + + window.SocialShareButton.prototype.share = function (platform) { + if (platform === "qrcode") { + this._emit("social_share_click", "share", { platform }); + this.renderQRPanel(); + this._emit("social_share_success", "share", { platform }); + + if (this.options.onShare) { + this.options.onShare(platform, this.options.url); + } + return; + } + + // Delegate all other platforms to the original handler + return originalShare.call(this, platform); + }; + + window.SocialShareButton.prototype.renderQRPanel = function () { + if (!this.modal) return; + + // Do not render twice + if (this.modal.querySelector(".social-share-qr-panel")) return; + + if (typeof window.qrcode === "undefined") { + console.error("qrcode-generator failed to load."); + return; + } + + // --- Generate QR data --- + var typeNumber = 0; // 0 = auto-detect + var errorCorrectionLevel = "M"; + var qr = window.qrcode(typeNumber, errorCorrectionLevel); + qr.addData(this.options.url); + qr.make(); + + var moduleCount = qr.getModuleCount(); + var cellSize = Math.max(3, Math.floor(180 / moduleCount)); + var margin = 2; + var size = moduleCount * cellSize + margin * 2 * cellSize; + + // --- Build DOM --- + var qrPanel = document.createElement("div"); + qrPanel.className = "social-share-qr-panel"; + + var title = document.createElement("h4"); + title.textContent = "Scan QR Code"; + + var canvas = document.createElement("canvas"); + canvas.className = "social-share-qr-canvas"; + canvas.width = size; + canvas.height = size; + + var ctx = canvas.getContext("2d"); + + // White background + ctx.fillStyle = "#ffffff"; + ctx.fillRect(0, 0, size, size); + + // Dark modules + ctx.fillStyle = "#000000"; + for (var row = 0; row < moduleCount; row++) { + for (var col = 0; col < moduleCount; col++) { + if (qr.isDark(row, col)) { + ctx.fillRect( + (col + margin) * cellSize, + (row + margin) * cellSize, + cellSize, + cellSize + ); + } + } + } + + var downloadBtn = document.createElement("button"); + downloadBtn.className = "social-share-qr-download"; + downloadBtn.textContent = "Download QR"; + + var self = this; + var downloadHandler = function () { + var dataUrl = canvas.toDataURL("image/png"); + var a = document.createElement("a"); + a.href = dataUrl; + a.download = "share-qrcode.png"; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + }; + + downloadBtn.addEventListener("click", downloadHandler); + // Register in central listener list so destroy() cleans it up + this.addEventListener(downloadBtn, "click", downloadHandler); + + this._qrDownloadHandler = downloadHandler; + this._qrDownloadBtn = downloadBtn; + + qrPanel.appendChild(title); + qrPanel.appendChild(canvas); + qrPanel.appendChild(downloadBtn); + + // Insert right after the platforms row + var platformsContainer = this.modal.querySelector(".social-share-platforms"); + if (platformsContainer && platformsContainer.parentNode) { + platformsContainer.parentNode.insertBefore(qrPanel, platformsContainer.nextSibling); + } else { + var content = this.modal.querySelector(".social-share-modal-content"); + if (content) content.appendChild(qrPanel); + } + }; + + window.SocialShareButton.prototype.closeModal = function () { + if (this.modal) { + var qrPanel = this.modal.querySelector(".social-share-qr-panel"); + if (qrPanel) { + if (this._qrDownloadBtn && this._qrDownloadHandler) { + this._qrDownloadBtn.removeEventListener("click", this._qrDownloadHandler); + // Purge from central registry + this.listeners = this.listeners.filter( + function (l) { return l.handler !== this._qrDownloadHandler; }, + this + ); + this._qrDownloadBtn = null; + this._qrDownloadHandler = null; + } + qrPanel.remove(); + } + } + return originalCloseModal.call(this); + }; + } // end applyQRPatch + + function loadQRCodeGenerator(callback) { + if (typeof window.qrcode !== "undefined") { + callback(); + return; + } + + var script = document.createElement("script"); + script.src = "https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"; + script.onload = callback; + script.onerror = function () { + console.error("Failed to load qrcode-generator from CDN."); + }; + document.head.appendChild(script); + } + + function init() { + loadQRCodeGenerator(function () { + applyQRPatch(); + }); + } + + // Wait for all deferred scripts to finish evaluating before loading and patching. + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } +})(); diff --git a/src/social-share-button.css b/src/social-share-button.css index 0609839b..e01cc916 100644 --- a/src/social-share-button.css +++ b/src/social-share-button.css @@ -465,3 +465,68 @@ display: none !important; } } +/* ----------------------------------------------------------------------------- + QR CODE PANEL + ----------------------------------------------------------------------------- */ + +.social-share-qr-panel { + display: flex; + flex-direction: column; + align-items: center; + padding: 16px; + border-top: 1px solid rgba(255, 255, 255, 0.1); +} + +.social-share-modal-overlay.light .social-share-qr-panel { + border-top-color: rgba(0, 0, 0, 0.1); +} + +.social-share-qr-panel h4 { + margin: 0 0 12px 0; + color: #fff; + font-size: 14px; + font-weight: 500; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; +} + +.social-share-modal-overlay.light .social-share-qr-panel h4 { + color: #333; +} + +.social-share-qr-canvas { + background: #fff; + padding: 8px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + margin-bottom: 16px; +} + +.social-share-modal-overlay.light .social-share-qr-canvas { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.social-share-qr-download { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + color: #fff; + padding: 8px 16px; + border-radius: 6px; + font-size: 13px; + cursor: pointer; + transition: all 0.2s ease; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; +} + +.social-share-qr-download:hover { + background: rgba(255, 255, 255, 0.15); +} + +.social-share-modal-overlay.light .social-share-qr-download { + background: #f0f0f0; + border-color: #ddd; + color: #333; +} + +.social-share-modal-overlay.light .social-share-qr-download:hover { + background: #e4e4e4; +} diff --git a/src/social-share-button.js b/src/social-share-button.js index 2dd6dd57..54dcf878 100644 --- a/src/social-share-button.js +++ b/src/social-share-button.js @@ -31,6 +31,7 @@ class SocialShareButton { "reddit", "pinterest", "discord", + "qrcode", ], theme: options.theme || "dark", buttonText: options.buttonText || "Share", @@ -196,6 +197,11 @@ class SocialShareButton { color: "#5865F2", icon: '', }, + qrcode: { + name: "QR Code", + color: "#000000", + icon: '', + }, }; return this.options.platforms From 1eb67eb8d41dc2487b594b4148e9e8f8c81a89f8 Mon Sep 17 00:00:00 2001 From: Aman kumar vishwakrma <186990161+amankv1234@users.noreply.github.com> Date: Wed, 5 Aug 2026 10:33:02 +0530 Subject: [PATCH 2/4] fix: address CodeRabbit review comments on QR extension --- README.md | 36 +++++++-- src/social-share-button-qr.js | 137 +++++++++++++++++++++++++--------- src/social-share-button.css | 2 +- src/social-share-button.js | 1 - 4 files changed, 133 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 0bfc4fd7..739771d7 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,9 @@ Lightweight social sharing component for web applications. Zero dependencies, fr ## Features -- 🌐 Multiple platforms: WhatsApp, Facebook, X, LinkedIn, Telegram, Reddit, Email, Pinterest, Discord, QR Code -- 🎯 Zero dependencies - pure vanilla JavaScript +- 🌐 Multiple platforms: WhatsApp, Facebook, X, LinkedIn, Telegram, Reddit, Email, Pinterest, Discord +- 📷 QR Code sharing (optional — requires the `social-share-button-qr.js` extension, which loads `qrcode-generator` at runtime) +- 🎯 Zero dependencies for the core library — pure vanilla JavaScript (QR extension loads `qrcode-generator` from jsDelivr CDN at runtime) - ⚛️ Framework support: React, Preact, Next.js, Qwik, Vue, Angular, or plain HTML - 🔄 Auto-detects current URL and page title - 📱 Fully responsive and mobile-ready @@ -527,7 +528,9 @@ new SocialShareButton({ | `onCopy` | function | `null` | Callback when user copies link: `(url) => {}` | **Available Platforms:** -`whatsapp`, `facebook`, `twitter`, `linkedin`, `telegram`, `reddit`, `email`, `pinterest`, `discord`, `qrcode` +`whatsapp`, `facebook`, `twitter`, `linkedin`, `telegram`, `reddit`, `email`, `pinterest`, `discord` + +> **QR Code** (`qrcode`) is available as an optional platform. It requires the separate `social-share-button-qr.js` extension — see [Using the QR Code Extension](#using-the-qr-code-extension). ### Customize Share Message/Post Text @@ -661,18 +664,39 @@ new SocialShareButton({ ### Using the QR Code Extension -To enable the QR Code feature, you must include the extension script in your HTML along with the main script: +The QR code feature is an **optional extension**. The core library has zero runtime dependencies; this extension loads `qrcode-generator` from jsDelivr at runtime. + +#### Via CDN (HTML) ```html - + + +``` + +#### Via npm / bundler + +```javascript +import "@aossie-org/social-share-button/src/social-share-button-qr.js"; +``` + +The extension will dynamically inject `qrcode-generator` from jsDelivr the first time the QR platform is initialised. + +#### Self-hosting under a strict CSP + +If your Content Security Policy (CSP) blocks `cdn.jsdelivr.net`, download `qrcode-generator` locally and load it **before** the extension: + +```html + + + ``` -When you pass `qrcode` in the `platforms` array, a QR code generation panel will be rendered inline inside the share modal when clicked. +When you pass `qrcode` in the `platforms` array, a QR code panel will be rendered inline inside the share modal with a canvas preview and a **Download QR** button. ### Using npm Package diff --git a/src/social-share-button-qr.js b/src/social-share-button-qr.js index 06b7710d..0bdf8dc8 100644 --- a/src/social-share-button-qr.js +++ b/src/social-share-button-qr.js @@ -4,9 +4,65 @@ */ (function () { + /** + * Shared bootstrap error helper for the QR extension. + * Keeps all console output in one place so it can be easily + * swapped for a project-level logger without touching call sites. + * + * @param {string} message + */ + function _qrWarn(message) { + if (typeof console !== "undefined" && typeof console.warn === "function") { + console.warn("[SocialShareButton QR] " + message); + } + } + + /** + * Cached Promise for the qrcode-generator CDN load. + * Guarantees only one