-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: add social sharing buttons to blog posts #4771
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
feat: add social sharing buttons to blog posts #4771
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Welcome to AsyncAPI. Thanks a lot for creating your first pull request. Please check out our contributors guide useful for opening a pull request.
Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out this issue.
✅ Deploy Preview for asyncapi-website ready!Built without sensitive environment variables
To edit notification comments on pull requests, go to your Netlify project configuration. |
📝 WalkthroughWalkthroughThe changes introduce social sharing functionality to blog posts by creating a new ShareDropdown component and integrating it into the BlogLayout header. The component provides sharing options for Twitter/X and LinkedIn, along with a copy-to-clipboard feature for the post URL. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
Pre-merge checks and finishing touches✅ Passed checks (5 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (2)
components/buttons/ShareDropdown.jsx (1)
23-36: Consider graceful fallback for browsers without Clipboard API support.While the Clipboard API is widely supported, older browsers or certain security contexts may not have it available. Currently, errors are only logged to the console.
🔎 Proposed enhancement
const handleCopy = (e) => { e.preventDefault(); const url = window.location.href; + if (!navigator.clipboard) { + setCopyStatus('Not supported'); + setTimeout(() => setCopyStatus('Copy Link'), 2000); + return; + } + navigator.clipboard.writeText(url).then(() => { setCopyStatus('Copied!'); setTimeout(() => { setCopyStatus('Copy Link'); setIsOpen(false); }, 2000); }).catch(err => { console.error('Failed to copy: ', err); + setCopyStatus('Failed'); + setTimeout(() => setCopyStatus('Copy Link'), 2000); }); };components/layout/BlogLayout.tsx (1)
93-93: Clarify or remove placeholder comment.The comment
{/* (Your existing script and style tags stay here) */}is unclear. If no scripts or styles were removed, this comment may be confusing. Consider removing it or being more specific about what it refers to.🔎 Suggested change
- {/* (Your existing script and style tags stay here) */} {post.canonical && <link rel='canonical' href={post.canonical} />}Or if there were previously scripts here (e.g., AddThis mentioned in the AI summary), add a more descriptive comment:
- {/* (Your existing script and style tags stay here) */} + {/* AddThis social sharing scripts removed in favor of custom ShareDropdown component */} {post.canonical && <link rel='canonical' href={post.canonical} />}
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (2)
components/buttons/ShareDropdown.jsxcomponents/layout/BlogLayout.tsx
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: akshatnema
Repo: asyncapi/website PR: 3262
File: components/navigation/BlogPostItem.tsx:80-87
Timestamp: 2024-10-11T11:17:32.246Z
Learning: In the `BlogPostItem` component, the parent `Link` wraps the entire content, so inner `Link` components around the title and excerpt are unnecessary.
📚 Learning: 2025-12-23T06:30:43.275Z
Learnt from: katara-Jayprakash
Repo: asyncapi/website PR: 4760
File: components/layout/GenericPostLayout.tsx:50-52
Timestamp: 2025-12-23T06:30:43.275Z
Learning: In the asyncapi/website repository, `GenericPostLayout` (components/layout/GenericPostLayout.tsx) is used for rendering about pages, not blog posts, even though it accepts a post typed as `IPosts['blog'][number]`. The type is reused for convenience. Blog posts use `BlogLayout.tsx` instead. When reviewing EditPageButton usage in GenericPostLayout, `contentType='about'` is the correct value.
Applied to files:
components/layout/BlogLayout.tsx
📚 Learning: 2024-10-11T11:17:32.246Z
Learnt from: akshatnema
Repo: asyncapi/website PR: 3262
File: components/navigation/BlogPostItem.tsx:80-87
Timestamp: 2024-10-11T11:17:32.246Z
Learning: In the `BlogPostItem` component, the parent `Link` wraps the entire content, so inner `Link` components around the title and excerpt are unnecessary.
Applied to files:
components/layout/BlogLayout.tsx
📚 Learning: 2024-10-11T11:32:30.226Z
Learnt from: akshatnema
Repo: asyncapi/website PR: 3262
File: components/navigation/BlogPostItem.tsx:95-119
Timestamp: 2024-10-11T11:32:30.226Z
Learning: In the `BlogPostItem` component (`components/navigation/BlogPostItem.tsx`), nesting `<a>` tags inside the parent `Link` component leads to hydration issues; therefore, we should avoid nesting `<a>` tags inside `Link` components in this component.
Applied to files:
components/layout/BlogLayout.tsx
📚 Learning: 2024-10-11T07:27:53.362Z
Learnt from: akshatnema
Repo: asyncapi/website PR: 3262
File: components/newsroom/FeaturedBlogPost.tsx:90-92
Timestamp: 2024-10-11T07:27:53.362Z
Learning: Using anchor tags for author names in 'FeaturedBlogPost' component leads to hydration issues on the website.
Applied to files:
components/layout/BlogLayout.tsx
🧬 Code graph analysis (1)
components/layout/BlogLayout.tsx (2)
components/AuthorAvatars.tsx (1)
AuthorAvatars(20-33)components/buttons/ShareDropdown.jsx (1)
ShareDropdown(7-101)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Lighthouse CI
🔇 Additional comments (3)
components/layout/BlogLayout.tsx (3)
15-15: Update import to reference .tsx file once ShareDropdown is converted.After converting
ShareDropdown.jsxtoShareDropdown.tsx, this import will automatically resolve correctly. No action needed now, but ensure the file conversion happens.
54-87: Verify responsive behavior of the flex layout on mobile devices.The
justify-betweenlayout places the ShareDropdown button on the far right. On smaller screens, this might cause the button to overflow or create awkward spacing if the author names are long.Test the layout on mobile viewports (320px-640px width) to ensure:
- The ShareDropdown button doesn't overflow or get cut off
- The spacing between author info and the button is appropriate
- Consider wrapping to a vertical layout on very small screens if needed
You can add responsive classes if issues are found:
<div className='mt-6 flex flex-col sm:flex-row items-start sm:items-center sm:justify-between gap-4'>
86-86: Good integration of ShareDropdown component.The component is properly integrated with the blog post title passed as a prop. The placement in the header alongside author information makes sense for easy discoverability.
| <button | ||
| onClick={() => setIsOpen(!isOpen)} | ||
| className="flex items-center justify-between px-3 py-1.5 h-9 w-28 rounded bg-[#bf21ba] hover:bg-[#a11ba0] transition-all duration-300 hover:-translate-y-1 active:scale-95 shadow-md group" | ||
| > | ||
| <svg className="w-4 h-4 text-white fill-current mr-2" viewBox="0 0 512 512"> | ||
| <path d="M384,336a63.78,63.78,0,0,0-46.12,19.7l-148-83.27a63.85,63.85,0,0,0,0-32.86l148-83.27a63.8,63.8,0,1,0-15.73-27.87l-148,83.27a64,64,0,1,0,0,88.6l148,83.27A64,64,0,1,0,384,336Z" /> | ||
| </svg> | ||
| <span className="text-[11px] font-bold text-white uppercase tracking-wider mx-auto"> | ||
| {isOpen ? 'Close' : 'Share'} | ||
| </span> | ||
| </button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add accessibility attributes to the toggle button.
The toggle button is missing ARIA attributes that are necessary for screen reader users to understand its purpose and state.
🔎 Proposed fix
<button
onClick={() => setIsOpen(!isOpen)}
+ aria-expanded={isOpen}
+ aria-haspopup="menu"
+ aria-label="Share this blog post"
className="flex items-center justify-between px-3 py-1.5 h-9 w-28 rounded bg-[#bf21ba] hover:bg-[#a11ba0] transition-all duration-300 hover:-translate-y-1 active:scale-95 shadow-md group"
>📝 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.
| <button | |
| onClick={() => setIsOpen(!isOpen)} | |
| className="flex items-center justify-between px-3 py-1.5 h-9 w-28 rounded bg-[#bf21ba] hover:bg-[#a11ba0] transition-all duration-300 hover:-translate-y-1 active:scale-95 shadow-md group" | |
| > | |
| <svg className="w-4 h-4 text-white fill-current mr-2" viewBox="0 0 512 512"> | |
| <path d="M384,336a63.78,63.78,0,0,0-46.12,19.7l-148-83.27a63.85,63.85,0,0,0,0-32.86l148-83.27a63.8,63.8,0,1,0-15.73-27.87l-148,83.27a64,64,0,1,0,0,88.6l148,83.27A64,64,0,1,0,384,336Z" /> | |
| </svg> | |
| <span className="text-[11px] font-bold text-white uppercase tracking-wider mx-auto"> | |
| {isOpen ? 'Close' : 'Share'} | |
| </span> | |
| </button> | |
| <button | |
| onClick={() => setIsOpen(!isOpen)} | |
| aria-expanded={isOpen} | |
| aria-haspopup="menu" | |
| aria-label="Share this blog post" | |
| className="flex items-center justify-between px-3 py-1.5 h-9 w-28 rounded bg-[#bf21ba] hover:bg-[#a11ba0] transition-all duration-300 hover:-translate-y-1 active:scale-95 shadow-md group" | |
| > | |
| <svg className="w-4 h-4 text-white fill-current mr-2" viewBox="0 0 512 512"> | |
| <path d="M384,336a63.78,63.78,0,0,0-46.12,19.7l-148-83.27a63.85,63.85,0,0,0,0-32.86l148-83.27a63.8,63.8,0,1,0-15.73-27.87l-148,83.27a64,64,0,1,0,0,88.6l148,83.27A64,64,0,1,0,384,336Z" /> | |
| </svg> | |
| <span className="text-[11px] font-bold text-white uppercase tracking-wider mx-auto"> | |
| {isOpen ? 'Close' : 'Share'} | |
| </span> | |
| </button> |
🤖 Prompt for AI Agents
In components/buttons/ShareDropdown.jsx around lines 45 to 55, the toggle button
lacks ARIA attributes and a button type; update the button to include
type="button", aria-haspopup="menu", aria-expanded={isOpen}, and an accessible
label such as aria-label={isOpen ? 'Close share menu' : 'Open share menu'} and
add aria-controls="share-dropdown" (or another chosen id) while ensuring the
dropdown menu element uses that same id so assistive tech can associate the
button with the menu.
| {isOpen && ( | ||
| <div className="absolute right-0 bottom-full mb-2 w-52 rounded-xl bg-white shadow-2xl border border-gray-100 py-2 z-50 overflow-hidden"> | ||
| <div className="px-4 py-2 text-[10px] font-bold text-gray-400 uppercase tracking-widest border-b border-gray-50 mb-1"> | ||
| Social Networks | ||
| </div> | ||
|
|
||
| <a | ||
| href={shareLinks.twitter} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="flex items-center px-4 py-3 text-sm text-gray-700 hover:bg-gray-50 hover:text-[#bf21ba] transition-colors group" | ||
| > | ||
| <svg className="w-4 h-4 mr-3 text-black fill-current group-hover:scale-110 transition-transform" viewBox="0 0 24 24"> | ||
| <path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" /> | ||
| </svg> | ||
| Twitter / X | ||
| </a> | ||
|
|
||
| <a | ||
| href={shareLinks.linkedin} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="flex items-center px-4 py-3 text-sm text-gray-700 hover:bg-gray-50 hover:text-[#bf21ba] transition-colors group" | ||
| > | ||
| <svg className="w-4 h-4 mr-3 text-[#0077b5] fill-current group-hover:scale-110 transition-transform" viewBox="0 0 24 24"> | ||
| <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" /> | ||
| </svg> | ||
| </a> | ||
|
|
||
| <button | ||
| onClick={handleCopy} | ||
| className="w-full flex items-center px-4 py-3 text-sm text-gray-700 hover:bg-gray-50 hover:text-[#bf21ba] transition-colors border-t border-gray-50 group" | ||
| > | ||
| <svg className="w-4 h-4 mr-3 text-gray-400 group-hover:text-[#bf21ba] transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2"> | ||
| <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> | ||
| <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> | ||
| </svg> | ||
| {copyStatus} | ||
| </button> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add keyboard navigation and ARIA role to dropdown menu.
The dropdown menu lacks keyboard support (Escape to close) and proper ARIA semantics. This limits accessibility for keyboard-only and screen reader users.
🔎 Proposed fix
Add keyboard handler:
// Close menu when clicking outside
useEffect(() => {
const handleClickOutside = (event) => {
if (menuRef.current && !menuRef.current.contains(event.target)) {
setIsOpen(false);
}
};
+ const handleEscape = (event: KeyboardEvent) => {
+ if (event.key === 'Escape' && isOpen) {
+ setIsOpen(false);
+ }
+ };
document.addEventListener('mousedown', handleClickOutside);
+ document.addEventListener('keydown', handleEscape);
- return () => document.removeEventListener('mousedown', handleClickOutside);
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ document.removeEventListener('keydown', handleEscape);
+ };
- }, []);
+ }, [isOpen]);Add role to dropdown:
{isOpen && (
- <div className="absolute right-0 bottom-full mb-2 w-52 rounded-xl bg-white shadow-2xl border border-gray-100 py-2 z-50 overflow-hidden">
+ <div role="menu" className="absolute right-0 bottom-full mb-2 w-52 rounded-xl bg-white shadow-2xl border border-gray-100 py-2 z-50 overflow-hidden">
<div className="px-4 py-2 text-[10px] font-bold text-gray-400 uppercase tracking-widest border-b border-gray-50 mb-1">Committable suggestion skipped: line range outside the PR's diff.
|
⚡️ Lighthouse report for the changes in this PR:
Lighthouse ran on https://deploy-preview-4771--asyncapi-website.netlify.app/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
components/buttons/ShareDropdown.tsx (3)
20-29: Consider adding Escape key handler for improved accessibility.While the outside-click handler works well, keyboard users would benefit from being able to close the dropdown with the Escape key. This was suggested in a previous review but not yet implemented.
🔎 Proposed enhancement
// Close menu when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (menuRef.current && !menuRef.current.contains(event.target as Node)) { setIsOpen(false); } }; + const handleEscape = (event: KeyboardEvent) => { + if (event.key === 'Escape' && isOpen) { + setIsOpen(false); + } + }; document.addEventListener('mousedown', handleClickOutside); + document.addEventListener('keydown', handleEscape); - return () => document.removeEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + document.removeEventListener('keydown', handleEscape); + }; - }, []); + }, [isOpen]);
31-42: Consider adding error handling for clipboard operations.The clipboard API can fail (e.g., permissions denied, insecure context). Adding a catch block would improve user experience by showing an error message instead of silent failure.
🔎 Proposed enhancement
const handleCopy = (e: React.MouseEvent<HTMLButtonElement>) => { e.preventDefault(); if (!currentUrl) return; - navigator.clipboard.writeText(currentUrl).then(() => { - setCopyStatus('Copied!'); - setTimeout(() => { - setCopyStatus('Copy Link'); - setIsOpen(false); - }, 2000); - }); + navigator.clipboard.writeText(currentUrl) + .then(() => { + setCopyStatus('Copied!'); + setTimeout(() => { + setCopyStatus('Copy Link'); + setIsOpen(false); + }, 2000); + }) + .catch(() => { + setCopyStatus('Failed to copy'); + setTimeout(() => setCopyStatus('Copy Link'), 2000); + }); };
55-68: Add explicit button type attribute.While the button includes proper ARIA attributes (good!), it's missing an explicit
type="button"attribute. This prevents unintended form submissions if the component is ever used within a form context.🔎 Proposed fix
<button + type="button" onClick={() => setIsOpen(!isOpen)} aria-haspopup="menu" aria-expanded={isOpen} aria-label="Share this blog post" className="flex items-center justify-between px-3 py-1.5 h-9 w-28 rounded bg-[#bf21ba] hover:bg-[#a11ba0] transition-all duration-300 hover:-translate-y-1 active:scale-95 shadow-md group" >
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
components/buttons/ShareDropdown.tsx
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-12-23T06:30:43.275Z
Learnt from: katara-Jayprakash
Repo: asyncapi/website PR: 4760
File: components/layout/GenericPostLayout.tsx:50-52
Timestamp: 2025-12-23T06:30:43.275Z
Learning: In the asyncapi/website repository, `GenericPostLayout` (components/layout/GenericPostLayout.tsx) is used for rendering about pages, not blog posts, even though it accepts a post typed as `IPosts['blog'][number]`. The type is reused for convenience. Blog posts use `BlogLayout.tsx` instead. When reviewing EditPageButton usage in GenericPostLayout, `contentType='about'` is the correct value.
Applied to files:
components/buttons/ShareDropdown.tsx
📚 Learning: 2024-10-11T11:32:30.226Z
Learnt from: akshatnema
Repo: asyncapi/website PR: 3262
File: components/navigation/BlogPostItem.tsx:95-119
Timestamp: 2024-10-11T11:32:30.226Z
Learning: In the `BlogPostItem` component (`components/navigation/BlogPostItem.tsx`), nesting `<a>` tags inside the parent `Link` component leads to hydration issues; therefore, we should avoid nesting `<a>` tags inside `Link` components in this component.
Applied to files:
components/buttons/ShareDropdown.tsx
📚 Learning: 2024-10-11T07:38:35.745Z
Learnt from: akshatnema
Repo: asyncapi/website PR: 3262
File: components/newsroom/FeaturedBlogPost.tsx:90-92
Timestamp: 2024-10-11T07:38:35.745Z
Learning: In Next.js, nested `<a>` tags cause hydration issues due to invalid HTML. To fix this, avoid nesting `<a>` tags by replacing inner `<a>` tags with non-interactive elements like `<span>`, and use click handlers to maintain interactivity if needed.
Applied to files:
components/buttons/ShareDropdown.tsx
📚 Learning: 2024-10-11T10:46:58.882Z
Learnt from: akshatnema
Repo: asyncapi/website PR: 3262
File: components/Avatar.tsx:35-44
Timestamp: 2024-10-11T10:46:58.882Z
Learning: In Next.js, when avoiding nested `<a>` tags due to hydration issues, use accessible non-link elements like `<button>` or `<span>` with appropriate roles, `tabIndex`, and event handlers to maintain accessibility and SEO.
Applied to files:
components/buttons/ShareDropdown.tsx
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Redirect rules - asyncapi-website
- GitHub Check: Header rules - asyncapi-website
- GitHub Check: Pages changed - asyncapi-website
- GitHub Check: Lighthouse CI
🔇 Additional comments (4)
components/buttons/ShareDropdown.tsx (4)
1-11: Excellent TypeScript conversion and SSR-safe state initialization.The component is now properly typed with a clean interface definition, and the addition of
currentUrlstate sets the foundation for SSR-safe URL handling. This addresses the previous concerns about TypeScript conversion.
13-18: SSR-safety properly implemented.The
typeof windowcheck ensures the URL is only accessed on the client side, completely resolving the previous SSR incompatibility issue. Well done!
44-51: Share links are correctly SSR-safe.By using
currentUrlstate instead of directly accessingwindow.location.href, the share links are now safe for server-side rendering. The proper use ofencodeURIComponentalso prevents URL injection issues.
70-107: Well-structured dropdown menu with proper ARIA semantics.The dropdown menu correctly implements
role="menu"withrole="menuitem"on interactive elements. The social sharing links include appropriate security attributes (rel="noopener noreferrer"), and the copy button is properly implemented as a button element.
What this PR does
Adds social sharing buttons (Twitter and LinkedIn and copy link ) to blog post pages.
Why this change is needed
This makes it easier for readers to share AsyncAPI blog posts on social media, improving reach and visibility.
How it was implemented
Related issue
Fixes #3649
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.