Skip to content

Fix: improved UI and accessibility of social share button#128

Open
Sneh30 wants to merge 1 commit intoAOSSIE-Org:mainfrom
Sneh30:fix/share-button-ui
Open

Fix: improved UI and accessibility of social share button#128
Sneh30 wants to merge 1 commit intoAOSSIE-Org:mainfrom
Sneh30:fix/share-button-ui

Conversation

@Sneh30
Copy link

@Sneh30 Sneh30 commented Mar 23, 2026

Overview

This pull request improves the overall user interface and accessibility of the Social Share Button component. The goal is to enhance usability, ensure better interaction across devices, and align the component with modern UI/UX standards.

Changes Made

  • Improved visual styling of social share buttons for better consistency
  • Enhanced accessibility by adding proper ARIA labels and improving keyboard navigation
  • Refactored minor UI elements for cleaner layout and responsiveness
  • Optimized user interaction for smoother experience

Why This Matters

These improvements make the component more user-friendly and inclusive. Enhanced accessibility ensures that users relying on assistive technologies can interact with the component effectively, while UI refinements improve overall engagement and usability.

How to Test

  1. Clone the repository and run the project locally

  2. Navigate to the Social Share Button component

  3. Verify:

    • Buttons render correctly across different screen sizes
    • Keyboard navigation works smoothly
    • Accessibility improvements (ARIA labels) are functioning
    • UI changes are visually consistent

Future Improvements

  • Add support for more social platforms
  • Further optimize performance and loading time
  • Enhance customization options for developers

Additional Notes

I would appreciate any feedback and am happy to make further improvements if required.

Summary by CodeRabbit

Release Notes

  • New Features

    • Built-in privacy-first analytics tracking for social share events with configurable sampling, deduplication, context enrichment, and event history management
    • New runtime methods for analytics inspection and control
    • Built-in analytics platform integrations ready to use
  • Documentation

    • Added comprehensive analytics configuration guide and code examples to README

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 23, 2026

Walkthrough

This PR introduces comprehensive built-in analytics capabilities to SocialShareButton, adding runtime configuration options, event history tracking, event ID generation, and two new built-in adapters for Google Tag Manager and console logging integration.

Changes

Cohort / File(s) Summary
Documentation & Configuration
.github/copilot/integrate-analytics.prompt.md, .github/copilot/integrate-social-share-button.prompt.md, README.md
Added documentation for new analytics feature including analyticsOptions parameter, runtime control methods (setAnalyticsOptions, getAnalyticsHistory, clearAnalyticsHistory), built-in adapters (GoogleTagManagerAdapter, ConsoleAdapter), and usage examples with sampling, deduplication, and context enrichment.
Analytics Implementation
src/social-share-analytics.js
Introduced GoogleTagManagerAdapter and ConsoleAdapter to the exported adapters registry, enabling event forwarding to Data Layer and console respectively.
Button Core Features
src/social-share-button.js
Implemented advanced analytics controls: added analyticsOptions configuration option, new public methods for history inspection and runtime option updates, event ID generation, and enhanced _emit() logic to support sampling, filtering, deduplication, context enrichment, and history buffer management with configurable limits.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

Typescript Lang, Documentation

Suggested reviewers

  • kpj2006

Poem

🐰 A button that tracks, with history so keen,
Analytics flowing, observability clean,
Sampling and deduping, context enriched bright,
Google and console adapters—analytics done right! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title claims 'improved UI and accessibility' but the actual changes are exclusively analytics-related: new analytics adapters, analytics configuration options, event history tracking, and analytics documentation. Update the PR title to accurately reflect the actual changes, such as 'Add built-in analytics adapters and runtime analytics controls' or 'Add GoogleTagManagerAdapter, ConsoleAdapter, and analytics configuration'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/copilot/integrate-analytics.prompt.md (1)

103-110: ⚠️ Potential issue | 🟡 Minor

CDN URL uses @main branch instead of version tag.

Line 106 references @main which points to the development branch. This may expose users to unreleased/unstable code. Consider using a specific version tag (e.g., @v1.0.4) once this feature is released.

Per retrieved learnings: "new wrapper files added in a PR are not part of any CDN release until the next version tag is cut."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/copilot/integrate-analytics.prompt.md around lines 103 - 110, Update
the CDN reference that uses the unstable branch by replacing the "@main" tag in
the social-share-analytics.js script URL with a specific release tag (e.g.,
"@v1.0.4") and update the accompanying comment to instruct using a versioned
tag; also audit the alternate npm import line
("social-share-button-aossie/src/social-share-analytics.js") to ensure it
references the published package name/path for the same released version so the
docs and the script tag consistently point to a released artifact instead of the
main branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 123-145: The README example references ConsoleAdapter but doesn't
show how to obtain or import it, causing a ReferenceError; update the snippet to
either (a) show the import/registration for ConsoleAdapter (e.g., an import or
adapter factory call) before new SocialShareButton(...) so ConsoleAdapter is
defined, or (b) add a short note and link to the analytics integration docs
explaining how to load ConsoleAdapter; reference the symbols ConsoleAdapter,
SocialShareButton, and analyticsPlugins so readers can see where to add the
adapter setup.

In `@src/social-share-analytics.js`:
- Around line 249-267: Add a brief inline comment above the
GoogleTagManagerAdapter.track method describing its purpose and behavior: state
that it guards against server-side execution (checks typeof window), ensures
window.dataLayer is an array, and then pushes a standardized analytics payload
to the GTM dataLayer, including optional conditional spreads for errorMessage
and context; update the comment to mention why the conditional spread operators
are used to avoid adding undefined fields.

In `@src/social-share-button.js`:
- Around line 784-792: Add a brief inline comment above the sampling conditional
that explains the sampling behavior in plain terms: compute sampleRate from
analyticsOptions.sampleRate (defaults to 1), treat any value > 1 as 100% (no
sampling), treat values between 0 and 1 as probabilistic sampling using
Math.random(), and short-circuit when sampleRate <= 0; also note that the
subsequent analyticsOptions.filter(payload) is an independent filter step. Place
this comment near the sampleRate declaration/conditional so readers of
analyticsOptions, sampleRate, and the payload/filter logic immediately
understand the edge cases.
- Around line 803-811: The current Array branch for analyticsOptions.dedupeBy
uses every() which returns false if any key is missing, silently disabling
dedupe; update the logic in src/social-share-button.js where sameEvent is set
for analyticsOptions.dedupeBy so that it first filters the dedupe keys to those
actually present on both payload and this._lastAnalyticsEvent (or alternatively
log a debug/warning for missing keys) and then compare only those existing keys
for equality; ensure you still handle the case where no valid keys remain (e.g.,
treat as not-duplicate or log) so deduplication behavior is explicit.
- Around line 695-701: Add a brief inline comment for the _generateEventId()
function that explains the event ID format and purpose: note that it returns a
base36 timestamp followed by a hyphen and an 8-character base36 random string
(timestamp for ordering + random suffix for uniqueness), and place the comment
immediately above the function declaration (not as JSDoc) so readers quickly
understand the ID structure used for emitted analytics events.

---

Outside diff comments:
In @.github/copilot/integrate-analytics.prompt.md:
- Around line 103-110: Update the CDN reference that uses the unstable branch by
replacing the "@main" tag in the social-share-analytics.js script URL with a
specific release tag (e.g., "@v1.0.4") and update the accompanying comment to
instruct using a versioned tag; also audit the alternate npm import line
("social-share-button-aossie/src/social-share-analytics.js") to ensure it
references the published package name/path for the same released version so the
docs and the script tag consistently point to a released artifact instead of the
main branch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c22be96c-71e9-40a3-a3cb-81c9bd68ee02

📥 Commits

Reviewing files that changed from the base of the PR and between 18d0b97 and 996e3a3.

📒 Files selected for processing (5)
  • .github/copilot/integrate-analytics.prompt.md
  • .github/copilot/integrate-social-share-button.prompt.md
  • README.md
  • src/social-share-analytics.js
  • src/social-share-button.js

Comment on lines +123 to +145
```js
const shareButton = new SocialShareButton({
container: "#share-button",
analyticsOptions: {
sampleRate: 0.6, // keep volume down in load tests
dedupeWindow: 3000, // suppress duplicate clicks in short windows
dedupeBy: ["eventName", "platform","url"],
historyLimit: 150,
enrichContext: true,
includeUserAgent: false,
},
analyticsPlugins: [new ConsoleAdapter()],
onAnalytics: (payload) => {
// custom filtering + forwarding
if (payload.eventName !== "social_share_popup_open") {
sendTelemetry(payload);
}
},
});

console.log(shareButton.getAnalyticsHistory());
shareButton.clearAnalyticsHistory();
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Example is incomplete — ConsoleAdapter usage not explained.

The code snippet uses new ConsoleAdapter() directly, but doesn't show how to load or access it. Users following this example will get a ReferenceError.

Consider adding the required setup, similar to how it's documented in the analytics prompt file:

+// Load the analytics adapters script first, then:
+const { ConsoleAdapter } = window.SocialShareAnalytics;
+
 const shareButton = new SocialShareButton({
   container: "#share-button",
   analyticsOptions: {
     sampleRate: 0.6,
     // ...
   },
-  analyticsPlugins: [new ConsoleAdapter()],
+  analyticsPlugins: [new ConsoleAdapter()],
   // ...
 });

Alternatively, link to the detailed analytics integration documentation.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```js
const shareButton = new SocialShareButton({
container: "#share-button",
analyticsOptions: {
sampleRate: 0.6, // keep volume down in load tests
dedupeWindow: 3000, // suppress duplicate clicks in short windows
dedupeBy: ["eventName", "platform","url"],
historyLimit: 150,
enrichContext: true,
includeUserAgent: false,
},
analyticsPlugins: [new ConsoleAdapter()],
onAnalytics: (payload) => {
// custom filtering + forwarding
if (payload.eventName !== "social_share_popup_open") {
sendTelemetry(payload);
}
},
});
console.log(shareButton.getAnalyticsHistory());
shareButton.clearAnalyticsHistory();
```
// Load the analytics adapters script first, then:
const { ConsoleAdapter } = window.SocialShareAnalytics;
const shareButton = new SocialShareButton({
container: "#share-button",
analyticsOptions: {
sampleRate: 0.6, // keep volume down in load tests
dedupeWindow: 3000, // suppress duplicate clicks in short windows
dedupeBy: ["eventName", "platform","url"],
historyLimit: 150,
enrichContext: true,
includeUserAgent: false,
},
analyticsPlugins: [new ConsoleAdapter()],
onAnalytics: (payload) => {
// custom filtering + forwarding
if (payload.eventName !== "social_share_popup_open") {
sendTelemetry(payload);
}
},
});
console.log(shareButton.getAnalyticsHistory());
shareButton.clearAnalyticsHistory();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 123 - 145, The README example references
ConsoleAdapter but doesn't show how to obtain or import it, causing a
ReferenceError; update the snippet to either (a) show the import/registration
for ConsoleAdapter (e.g., an import or adapter factory call) before new
SocialShareButton(...) so ConsoleAdapter is defined, or (b) add a short note and
link to the analytics integration docs explaining how to load ConsoleAdapter;
reference the symbols ConsoleAdapter, SocialShareButton, and analyticsPlugins so
readers can see where to add the adapter setup.

Comment on lines +249 to +267
class GoogleTagManagerAdapter extends SocialShareAnalyticsPlugin {
track(payload) {
if (typeof window === "undefined" || !Array.isArray(window.dataLayer)) {
return;
}
window.dataLayer.push({
event: payload.eventName,
source: payload.source,
interactionType: payload.interactionType,
platform: payload.platform,
url: payload.url,
title: payload.title,
componentId: payload.componentId,
timestamp: payload.timestamp,
...(payload.errorMessage ? { errorMessage: payload.errorMessage } : {}),
...(payload.context ? { context: payload.context } : {}),
});
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Add inline comment for track() method.

Per coding guidelines for src/**/*.{js,jsx}, newly added methods should have brief inline comments explaining what they do, especially for non-obvious logic like the conditional spread operators.

📝 Suggested comment
 class GoogleTagManagerAdapter extends SocialShareAnalyticsPlugin {
+  // Pushes analytics event to GTM dataLayer; includes errorMessage/context only when present
   track(payload) {
     if (typeof window === "undefined" || !Array.isArray(window.dataLayer)) {
       return;
     }

Based on learnings: "Flag any newly added or modified function that lacks a descriptive comment explaining what it does."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/social-share-analytics.js` around lines 249 - 267, Add a brief inline
comment above the GoogleTagManagerAdapter.track method describing its purpose
and behavior: state that it guards against server-side execution (checks typeof
window), ensures window.dataLayer is an array, and then pushes a standardized
analytics payload to the GTM dataLayer, including optional conditional spreads
for errorMessage and context; update the comment to mention why the conditional
spread operators are used to avoid adding undefined fields.

Comment on lines +695 to +701
/**
* Generates a short unique event ID for each emitted analytics event.
* @returns {string}
*/
_generateEventId() {
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider adding brief inline comment explaining the ID format.

Per coding guidelines, inline comments are preferred over JSDoc for this codebase. A short comment explaining the format would aid maintainability:

📝 Suggested enhancement
   _generateEventId() {
+    // Format: {base36-timestamp}-{random-8-chars} for lightweight client-side uniqueness
     return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
   }

As per coding guidelines: "Add comments for logical blocks explaining what they do."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/social-share-button.js` around lines 695 - 701, Add a brief inline
comment for the _generateEventId() function that explains the event ID format
and purpose: note that it returns a base36 timestamp followed by a hyphen and an
8-character base36 random string (timestamp for ordering + random suffix for
uniqueness), and place the comment immediately above the function declaration
(not as JSDoc) so readers quickly understand the ID structure used for emitted
analytics events.

Comment on lines +784 to +792
// Analytics sample rate and filtering
const sampleRate = Number(analyticsOptions.sampleRate || 1);
if (sampleRate <= 0 || sampleRate > 0 && sampleRate < 1 && Math.random() > sampleRate) {
return;
}

if (typeof analyticsOptions.filter === "function" && !analyticsOptions.filter(payload)) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Add inline comment explaining sampling logic.

The conditional on line 786 is correct but dense. An inline comment would clarify the edge cases (especially that values > 1 are treated as 100%):

📝 Suggested enhancement
     // Analytics sample rate and filtering
+    // sampleRate <= 0: disabled; 0 < sampleRate < 1: probabilistic; sampleRate >= 1: 100% pass-through
     const sampleRate = Number(analyticsOptions.sampleRate || 1);
     if (sampleRate <= 0 || sampleRate > 0 && sampleRate < 1 && Math.random() > sampleRate) {
       return;
     }

As per coding guidelines: "Add comments for edge cases and non-obvious logic."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Analytics sample rate and filtering
const sampleRate = Number(analyticsOptions.sampleRate || 1);
if (sampleRate <= 0 || sampleRate > 0 && sampleRate < 1 && Math.random() > sampleRate) {
return;
}
if (typeof analyticsOptions.filter === "function" && !analyticsOptions.filter(payload)) {
return;
}
// Analytics sample rate and filtering
// sampleRate <= 0: disabled; 0 < sampleRate < 1: probabilistic; sampleRate >= 1: 100% pass-through
const sampleRate = Number(analyticsOptions.sampleRate || 1);
if (sampleRate <= 0 || sampleRate > 0 && sampleRate < 1 && Math.random() > sampleRate) {
return;
}
if (typeof analyticsOptions.filter === "function" && !analyticsOptions.filter(payload)) {
return;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/social-share-button.js` around lines 784 - 792, Add a brief inline
comment above the sampling conditional that explains the sampling behavior in
plain terms: compute sampleRate from analyticsOptions.sampleRate (defaults to
1), treat any value > 1 as 100% (no sampling), treat values between 0 and 1 as
probabilistic sampling using Math.random(), and short-circuit when sampleRate <=
0; also note that the subsequent analyticsOptions.filter(payload) is an
independent filter step. Place this comment near the sampleRate
declaration/conditional so readers of analyticsOptions, sampleRate, and the
payload/filter logic immediately understand the edge cases.

Comment on lines +803 to +811
if (typeof analyticsOptions.dedupeBy === "function") {
sameEvent = analyticsOptions.dedupeBy(this._lastAnalyticsEvent, payload);
} else if (Array.isArray(analyticsOptions.dedupeBy)) {
sameEvent = analyticsOptions.dedupeBy.every((key) =>
Object.prototype.hasOwnProperty.call(payload, key)
? this._lastAnalyticsEvent[key] === payload[key]
: false
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Edge case: non-existent keys in dedupeBy array disable deduplication entirely.

If a developer specifies a key that doesn't exist in the payload (e.g., dedupeBy: ["eventName", "typoKey"]), the every() will always return false, effectively disabling deduplication silently.

Consider either logging a debug warning for invalid keys, or skipping non-existent keys:

📝 Alternative approach that skips missing keys
       } else if (Array.isArray(analyticsOptions.dedupeBy)) {
-        sameEvent = analyticsOptions.dedupeBy.every((key) =>
-          Object.prototype.hasOwnProperty.call(payload, key)
-            ? this._lastAnalyticsEvent[key] === payload[key]
-            : false
-        );
+        // Only compare keys that exist in both payloads
+        const validKeys = analyticsOptions.dedupeBy.filter(
+          (key) => Object.prototype.hasOwnProperty.call(payload, key)
+        );
+        sameEvent = validKeys.length > 0 && validKeys.every(
+          (key) => this._lastAnalyticsEvent[key] === payload[key]
+        );
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/social-share-button.js` around lines 803 - 811, The current Array branch
for analyticsOptions.dedupeBy uses every() which returns false if any key is
missing, silently disabling dedupe; update the logic in
src/social-share-button.js where sameEvent is set for analyticsOptions.dedupeBy
so that it first filters the dedupe keys to those actually present on both
payload and this._lastAnalyticsEvent (or alternatively log a debug/warning for
missing keys) and then compare only those existing keys for equality; ensure you
still handle the case where no valid keys remain (e.g., treat as not-duplicate
or log) so deduplication behavior is explicit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant