npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@scribe-atp/social

v1.4.1

Published

React components for Scribe social interactions (Like, Subscribe).

Downloads

45

Readme

@scribe-atp/social

npm license

React components for adding social interactions — likes, shares, and subscriptions — to Scribe CMS articles. Works with any React-based framework (React Router, Next.js, etc.).

Interactions are powered by the AT Protocol via social.scribe-atp.app. When a user clicks a button, a popup opens for them to sign in with their Bluesky account. The result is written to their AT Protocol repository and reported back to the originating page.

Installation

npm install @scribe-atp/social

Requires React 18 or later as a peer dependency.

Components

LikeButton

Creates an AT Protocol app.bsky.feed.like record ("recommend") for an article when clicked.

import { LikeButton } from "@scribe-atp/social";

<LikeButton
  documentUri="at://did:plc:abc123/site.standard.document/3jxtctq7kqm2y"
  publicationUri="at://did:plc:abc123/site.standard.publication/3mp4nd46xwr2h"
  title="My Article Title"
/>

| Prop | Type | Required | Description | | ---- | ---- | -------- | ----------- | | documentUri | string | ✓ | AT URI of the site.standard.document record | | publicationUri | string | ✓ | AT URI of the site.standard.publication record | | title | string | ✓ | Article title — shown in the like popup | | serviceUrl | string | — | Override the social service URL. Defaults to https://social.scribe-atp.app | | className | string | — | Additional CSS class appended to the base scribe-atp-like-button class | | children | ReactNode \| ((isLiked: boolean) => ReactNode) | — | Custom button label. Omit to use the defaults "Like" / "Liked ✓" | | onSuccess | () => void | — | Called after a successful like | | defaultLiked | boolean | — | Initial liked state. Pass true from an SSR loader to avoid a flash of unconfirmed state on first render |

Customising the label

Pass a render prop to access the internal liked state:

<LikeButton documentUri={documentUri} publicationUri={publicationUri} title={title}>
  {(isLiked) => (isLiked ? "Loved it ✓" : "Did you enjoy this?")}
</LikeButton>

Or pass a static node to replace the label entirely:

<LikeButton documentUri={documentUri} publicationUri={publicationUri} title={title}>
  ♥ Recommend this article
</LikeButton>

ShareButton

Opens a popup for the user to share the article via their Bluesky account.

import { ShareButton } from "@scribe-atp/social";

<ShareButton
  documentUri="at://did:plc:abc123/site.standard.document/3jxtctq7kqm2y"
  publicationUri="at://did:plc:abc123/site.standard.publication/3xyz789"
  title="My Article Title"
/>

| Prop | Type | Required | Description | | ---- | ---- | -------- | ----------- | | documentUri | string | ✓ | AT URI of the site.standard.document record | | publicationUri | string | ✓ | AT URI of the site.standard.publication record | | title | string | ✓ | Article title — shown in the share popup | | canonicalUrl | string | — | Canonical URL of the article. Defaults to window.location.href | | serviceUrl | string | — | Override the social service URL. Defaults to https://social.scribe-atp.app | | className | string | — | Additional CSS class appended to the base scribe-atp-share-button class | | children | ReactNode \| ((isShared: boolean) => ReactNode) | — | Custom button label. Omit to use the defaults "Share" / "Shared ✓" | | onSuccess | () => void | — | Called after a successful share |

Customising the label

<ShareButton documentUri={documentUri} publicationUri={publicationUri} title={title}>
  {(isShared) => (isShared ? "Thanks for sharing! ✓" : "Share this article")}
</ShareButton>

Note: after a successful share the button briefly enters its confirmed state before resetting, allowing the user to share again.

SubscribeButton

Follows the author's publication on the AT Protocol when clicked.

import { SubscribeButton } from "@scribe-atp/social";

<SubscribeButton
  publicationUri="at://did:plc:abc123/site.standard.publication/3xyz789"
  title="My Site"
/>

| Prop | Type | Required | Description | | ---- | ---- | -------- | ----------- | | publicationUri | string | ✓ | AT URI of the site.standard.publication record | | title | string | ✓ | Publication name — shown in the subscribe popup | | serviceUrl | string | — | Override the social service URL. Defaults to https://social.scribe-atp.app | | className | string | — | Additional CSS class appended to the base scribe-atp-subscribe-button class | | children | ReactNode \| ((isSubscribed: boolean) => ReactNode) | — | Custom button label. Omit to use the defaults "Subscribe" / "Subscribed ✓" | | onSuccess | () => void | — | Called after a successful subscription | | defaultSubscribed | boolean | — | Initial subscribed state. Pass true from an SSR loader to avoid a flash of unconfirmed state on first render |

Customising the label

<SubscribeButton publicationUri={publicationUri} title={title}>
  {(isSubscribed) => (isSubscribed ? "Following ✓" : "Follow this site")}
</SubscribeButton>

Reacting to success

All three buttons accept an onSuccess callback fired after the action completes. Use it to show a toast, fire an analytics event, or update surrounding UI:

<LikeButton
  documentUri={documentUri}
  publicationUri={publicationUri}
  title={article.title}
  onSuccess={() => toast("Thanks for the like!")}
/>

<SubscribeButton
  publicationUri={publicationUri}
  title="My Site"
  onSuccess={() => analytics.track("subscribe")}
/>

SSR / avoiding flash of unconfirmed state

On SSR frameworks (React Router, Next.js, Nuxt), LikeButton and SubscribeButton initialise in their unconfirmed state because localStorage is unavailable at render time. If you can determine the state server-side (e.g. from a cookie), pass it via defaultLiked / defaultSubscribed to skip the client-side flash:

// app/routes/blog.$slug.tsx (React Router loader)
export async function loader({ request, params }) {
  const cookies = parseCookies(request.headers.get("Cookie") ?? "");
  const defaultLiked = cookies[`scribe:recommended:${documentUri}`] === "1";
  const defaultSubscribed = cookies[`scribe:subscribed:${publicationUri}`] === "1";
  return { ..., defaultLiked, defaultSubscribed };
}

// In your component:
<LikeButton
  documentUri={documentUri}
  publicationUri={publicationUri}
  title={article.title}
  defaultLiked={defaultLiked}
/>

When defaultLiked / defaultSubscribed is provided, the component uses it as the initial state and skips the localStorage read on mount.

How it works

  1. The user clicks a button. A popup window opens at social.scribe-atp.app with the relevant AT URI and a one-time token.
  2. The user signs in with their Bluesky account via AT Protocol OAuth.
  3. On success, the service writes the like, share, or follow record, then notifies the originating page — via postMessage if the popup has access to window.opener, or via polling /status/:token as a fallback.
  4. The button updates to its confirmed state. For LikeButton and SubscribeButton the result is persisted to localStorage; for ShareButton the confirmed state resets after 3 seconds.

Accessibility

LikeButton and SubscribeButton use aria-pressed to communicate their confirmed state. The button remains focusable and in the tab order after being activated — screen readers will announce the pressed state without the button needing to disappear or become disabled.

ShareButton uses disabled during its brief 3-second confirmed window since the state is transient rather than a toggle.

Getting the AT URIs

Use fetchArticleBySlug and fetchSite from @scribe-atp/core to obtain the URIs needed by the components:

import { fetchArticleBySlug, fetchSite } from "@scribe-atp/core";

const [{ uri: documentUri }, site] = await Promise.all([
  fetchArticleBySlug(author, siteUrl, articleSlug, signal),
  fetchSite(author, siteUrl, signal),
]);

// documentUri → LikeButton, ShareButton
// site.uri    → LikeButton, ShareButton, SubscribeButton

Storage utilities

Exported for cases where you need to read or set liked/subscribed state outside the components:

import { isRecommended, markRecommended, isSubscribed, markSubscribed } from "@scribe-atp/social";

isRecommended("at://...");  // → boolean
markRecommended("at://...");

isSubscribed("at://...");   // → boolean
markSubscribed("at://...");

localStorage access is wrapped in a try/catch — safe to call during SSR where storage is unavailable.

State is stored under the keys scribe:recommended:{documentUri} and scribe:subscribed:{publicationUri}.

License

MIT