feat: implement social share buttons

This commit is contained in:
fredy.siswanto 2025-03-08 20:25:41 +07:00
parent 46e009f888
commit 1881032ed2
4 changed files with 95 additions and 53 deletions

View File

@ -1,59 +1,70 @@
import type { FC } from 'react' import { LinkIcon } from '@heroicons/react/20/solid'
import { Link } from 'react-router' import { useState } from 'react'
import { twMerge } from 'tailwind-merge' import {
FacebookShareButton,
LinkedinShareButton,
TwitterShareButton,
InstapaperShareButton,
} from 'react-share'
import { FacebookIcon } from '~/components/icons/facebook' import { FacebookIcon } from '~/components/icons/facebook'
import { InstagramIcon } from '~/components/icons/instagram' import { InstagramIcon } from '~/components/icons/instagram'
import { LinkIcon } from '~/components/icons/link-icon'
import { LinkedinIcon } from '~/components/icons/linkedin' import { LinkedinIcon } from '~/components/icons/linkedin'
import { XIcon } from '~/components/icons/x' import { XIcon } from '~/components/icons/x'
type SocialMediaProperties = { type SocialShareButtonsProperties = {
className?: string url: string
slug?: string title: string
} }
const dataSocialMedia = [ export const SocialShareButtons = ({
{ url,
type: 'link', title,
url: 'post-id/', }: SocialShareButtonsProperties) => {
icon: LinkIcon, const [showPopup, setShowPopup] = useState(false)
},
{
type: 'facebook',
url: 'https://facebook.com/',
icon: FacebookIcon,
},
{
type: 'linkedin',
url: 'https://linkedin.com/',
icon: LinkedinIcon,
},
{
type: 'x',
url: 'https://x.com/',
icon: XIcon,
},
{
type: 'instagram',
url: 'https://instagram.com/',
icon: InstagramIcon,
},
]
export const IconsSocial: FC<SocialMediaProperties> = ({ className }) => { const handleCopyLink = () => {
navigator.clipboard.writeText(url)
setShowPopup(true)
setTimeout(() => setShowPopup(false), 2000)
}
return ( return (
<div className={twMerge('flex gap-2', className)}> <div className="flex items-center space-x-2">
{dataSocialMedia.map(({ url, icon: Icon }, index) => ( {showPopup && (
<Link <div className="absolute top-0 rounded-lg border-2 border-gray-400 bg-white p-2 shadow-lg">
key={index} Link berhasil disalin!
to={url} </div>
target="_blank" )}
rel="noopener noreferrer" <button onClick={handleCopyLink}>
> <LinkIcon className="h-8 w-8 rounded-full bg-gray-400 p-2 sm:h-10 sm:w-10" />
<Icon className="h-8 w-8 rounded-full bg-gray-400 p-2 sm:h-10 sm:w-10" /> </button>
</Link> <FacebookShareButton
))} url={url}
title={title}
>
<FacebookIcon className="h-8 w-8 rounded-full bg-gray-400 p-2 sm:h-10 sm:w-10" />
</FacebookShareButton>
<LinkedinShareButton
url={url}
title={title}
>
<LinkedinIcon className="h-8 w-8 rounded-full bg-gray-400 p-2 sm:h-10 sm:w-10" />
</LinkedinShareButton>
<TwitterShareButton
url={url}
title={title}
>
<XIcon className="h-8 w-8 rounded-full bg-gray-400 p-2 sm:h-10 sm:w-10" />
</TwitterShareButton>
<InstapaperShareButton
url={url}
title={title}
>
<InstagramIcon className="h-8 w-8 rounded-full bg-gray-400 p-2 sm:h-10 sm:w-10" />
</InstapaperShareButton>
</div> </div>
) )
} }

View File

@ -4,7 +4,7 @@ import { useRouteLoaderData } from 'react-router'
import type { TTagResponse } from '~/apis/common/get-tags' import type { TTagResponse } from '~/apis/common/get-tags'
import { Card } from '~/components/ui/card' import { Card } from '~/components/ui/card'
import { CarouselSection } from '~/components/ui/carousel-section' import { CarouselSection } from '~/components/ui/carousel-section'
import { IconsSocial } from '~/components/ui/social-share' import { SocialShareButtons } from '~/components/ui/social-share'
import { BERITA } from '~/data/contents' import { BERITA } from '~/data/contents'
import type { loader } from '~/routes/_news.detail.$slug' import type { loader } from '~/routes/_news.detail.$slug'
import { formatDate } from '~/utils/formatter' import { formatDate } from '~/utils/formatter'
@ -13,10 +13,10 @@ export const NewsDetailPage = () => {
const loaderData = useRouteLoaderData<typeof loader>( const loaderData = useRouteLoaderData<typeof loader>(
'routes/_news.detail.$slug', 'routes/_news.detail.$slug',
) )
const currentUrl = globalThis.location
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const { newsDetailData }: any = loaderData const { newsDetailData }: any = loaderData
const { title, content, featured_image, slug, author, live_at, tags } = const { title, content, featured_image, author, live_at, tags } =
newsDetailData newsDetailData
return ( return (
@ -40,7 +40,11 @@ export const NewsDetailPage = () => {
<p className="text-sm">{formatDate(live_at)} . 5 min read </p> <p className="text-sm">{formatDate(live_at)} . 5 min read </p>
</div> </div>
</div> </div>
<IconsSocial className="flex-row" /> {/* <IconsSocial className="flex-row" /> */}
<SocialShareButtons
url={`${currentUrl}`}
title={title}
/>
</div> </div>
{/* end next planing create component for this section */} {/* end next planing create component for this section */}
<div className="w-full bg-amber-200"> <div className="w-full bg-amber-200">
@ -59,9 +63,10 @@ export const NewsDetailPage = () => {
<div className="items-end justify-between border-b-gray-300 py-4 sm:flex"> <div className="items-end justify-between border-b-gray-300 py-4 sm:flex">
<div className="flex flex-col max-sm:mb-3"> <div className="flex flex-col max-sm:mb-3">
<p className="mb-2">Share this post</p> <p className="mb-2">Share this post</p>
<IconsSocial
className="a" <SocialShareButtons
slug={slug} url={`${currentUrl}`}
title={title}
/> />
</div> </div>
<div className="flex flex-wrap items-end gap-2"> <div className="flex flex-wrap items-end gap-2">

View File

@ -43,6 +43,7 @@
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-hook-form": "^7.54.2", "react-hook-form": "^7.54.2",
"react-router": "^7.1.3", "react-router": "^7.1.3",
"react-share": "^5.2.2",
"remix-hook-form": "^6.1.3", "remix-hook-form": "^6.1.3",
"tailwind-merge": "^3.0.1", "tailwind-merge": "^3.0.1",
"xior": "^0.6.3", "xior": "^0.6.3",

25
pnpm-lock.yaml generated
View File

@ -95,6 +95,9 @@ importers:
react-router: react-router:
specifier: ^7.1.3 specifier: ^7.1.3
version: 7.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0) version: 7.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react-share:
specifier: ^5.2.2
version: 5.2.2(react@19.0.0)
remix-hook-form: remix-hook-form:
specifier: ^6.1.3 specifier: ^6.1.3
version: 6.1.3(react-dom@19.0.0(react@19.0.0))(react-hook-form@7.54.2(react@19.0.0))(react-router@7.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) version: 6.1.3(react-dom@19.0.0(react@19.0.0))(react-hook-form@7.54.2(react@19.0.0))(react-router@7.1.3(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
@ -3130,6 +3133,9 @@ packages:
jsonfile@6.1.0: jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
jsonp@0.2.1:
resolution: {integrity: sha512-pfog5gdDxPdV4eP7Kg87M8/bHgshlZ5pybl+yKxAnCZ5O7lCIn7Ixydj03wOlnDQesky2BPyA91SQ+5Y/mNwzw==}
jsonparse@1.3.1: jsonparse@1.3.1:
resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
engines: {'0': node >= 0.2.0} engines: {'0': node >= 0.2.0}
@ -3945,6 +3951,11 @@ packages:
react-dom: react-dom:
optional: true optional: true
react-share@5.2.2:
resolution: {integrity: sha512-z0nbOX6X6vHHWAvXduNkYeJUKTKNpKM5Xpmc5a2BxjJhUWl+sE7AsSEMmYEUj2DuDjZr5m7KFIGF0sQPKcUN6w==}
peerDependencies:
react: ^17 || ^18 || ^19
react-simple-animate@3.5.3: react-simple-animate@3.5.3:
resolution: {integrity: sha512-Ob+SmB5J1tXDEZyOe2Hf950K4M8VaWBBmQ3cS2BUnTORqHjhK0iKG8fB+bo47ZL15t8d3g/Y0roiqH05UBjG7A==} resolution: {integrity: sha512-Ob+SmB5J1tXDEZyOe2Hf950K4M8VaWBBmQ3cS2BUnTORqHjhK0iKG8fB+bo47ZL15t8d3g/Y0roiqH05UBjG7A==}
peerDependencies: peerDependencies:
@ -7894,6 +7905,12 @@ snapshots:
optionalDependencies: optionalDependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
jsonp@0.2.1:
dependencies:
debug: 2.6.9
transitivePeerDependencies:
- supports-color
jsonparse@1.3.1: {} jsonparse@1.3.1: {}
jsx-ast-utils@3.3.5: jsx-ast-utils@3.3.5:
@ -8683,6 +8700,14 @@ snapshots:
optionalDependencies: optionalDependencies:
react-dom: 19.0.0(react@19.0.0) react-dom: 19.0.0(react@19.0.0)
react-share@5.2.2(react@19.0.0):
dependencies:
classnames: 2.5.1
jsonp: 0.2.1
react: 19.0.0
transitivePeerDependencies:
- supports-color
react-simple-animate@3.5.3(react-dom@19.0.0(react@19.0.0)): react-simple-animate@3.5.3(react-dom@19.0.0(react@19.0.0)):
dependencies: dependencies:
react-dom: 19.0.0(react@19.0.0) react-dom: 19.0.0(react@19.0.0)