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

sanity-plugin-sgntech-release

v1.1.0

Published

Album, EP and single blocks for Sanity Studio — cover, facts and tracklist inside an article, with placeholders for tracks that have not been announced yet.

Readme


What you get

  • A releaseEmbed block for Portable Text: cover on one side, title, artist, kind of release and label on the other, tracklist underneath.
  • Half-known tracklists. Every position is listed; tracks that have not been announced are marked as such and render as a placeholder bar. Two singles out and the rest under wraps is a normal state, not a broken one.
  • The release state comes from the date, never from a checkbox: a date in the future reads "out on", a date in the past reads "released", a rough date like Spring 2027 reads "announced for", nothing at all reads "to be announced". Nobody has to flip anything on release day.
  • Tracklist optional per embed. A switch on the block decides whether the list shows up — the same album can appear with its full tracklist in one article and as a bare cover card in another.
  • Optional link to the release page. Either a URL typed in the Studio or one built by your own routing, applied to cover and title.
  • A release document type, so one album is maintained in one place and every article embedding it updates at once — or type the fields straight into the block for a one-off mention.
  • Optional reference to your own artist documents; plain text otherwise.
  • sanity-plugin-sgntech-release/render — the React renderer. That entry point does not import sanity, so your app bundle stays clean.
  • Design-agnostic. The stylesheet only carries the two-column frame and the list — no colours, no fonts. Everything else is yours.

Installation

npm install sanity-plugin-sgntech-release

Requires Sanity Studio v5 or v6 and React 18 or 19. No runtime dependencies.

📦 On npm: sanity-plugin-sgntech-release

Usage in the Studio

// sanity.config.ts
import {defineArrayMember, defineConfig, defineField} from 'sanity'
import {releaseEmbed} from 'sanity-plugin-sgntech-release'

export default defineConfig({
  // ...
  plugins: [releaseEmbed()],

  schema: {
    types: [
      {
        type: 'document',
        name: 'article',
        fields: [
          defineField({
            type: 'array',
            name: 'body',
            of: [
              defineArrayMember({type: 'block'}),
              defineArrayMember({type: 'releaseEmbed'}), // ← the release block
            ],
          }),
        ],
      },
    ],
  },
})

The plugin registers three types: the release document, the releaseEmbed block and the releaseTrack object used by both.

Options

releaseEmbed({
  releaseType: 'release', // document type name
  blockType: 'releaseEmbed', // block type name
  trackType: 'releaseTrack', // track object type name
  artistType: undefined, // e.g. 'band' — adds a reference field for the artist
  registerDocumentType: true, // false if your Studio already has a release type
  allowReference: true, // offer the reference field on the block
  allowInline: true, // offer the inline fields on the block
  disableNew: false, // cover: pick from the Media Library only
})

Editors get two switches on the block itself: Show tracklist and an optional Link to the release page.

Reference or inline

The block offers both. Picking a release hides the inline fields, so the same album is never maintained twice in one block. When both carry a value, the reference wins — field by field, so an inline value can still fill a gap the referenced document leaves open.

Stored value

{
  "_type": "releaseEmbed",
  "_key": "a1b2",
  "title": "Unnamed album",
  "artistName": "A Band",
  "type": "album",
  "label": "Indie Records",
  "releaseDate": "2027-03-05",
  "releaseDateText": "Spring 2027",
  "cover": {"_type": "image", "asset": {"_ref": "image-…"}, "alt": "Cover"},
  "tracks": [
    {"_key": "t1", "title": "First single", "isSingle": true, "duration": "3:12"},
    {"_key": "t2", "unknown": true},
    {"_key": "t3", "title": "Second single", "featuring": ["Guest singer"], "isSingle": true}
  ]
}

Rendering in your frontend

Fetch the block with the cover and — if used — the reference and the artist dereferenced:

body[]{
  ...,
  _type == "releaseEmbed" => {
    ...,
    cover{..., asset->{url, altText}},
    release->{..., slug, cover{..., asset->{url, altText}}, artist->{name}}
  }
}

With @portabletext/react

import {PortableText} from '@portabletext/react'
import {releasePortableTextComponents} from 'sanity-plugin-sgntech-release/render'

;<PortableText value={article.body} components={releasePortableTextComponents} />

Standalone component

import {Release} from 'sanity-plugin-sgntech-release/render'

;<Release value={block} titleAs="h2" formatDate={(date) => date.toLocaleDateString('en-GB')} />

With @sanity/image-url or next/image

import imageUrlBuilder from '@sanity/image-url'
import Image from 'next/image'

const builder = imageUrlBuilder(client)

;<Release
  value={block}
  resolveImageUrl={(cover) => builder.image(cover).width(600).auto('format').url()}
  renderCover={({src, alt}) => <Image src={src!} alt={alt} width={600} height={600} />}
/>

Props

| Prop | Default | Purpose | | ----------------- | ------------ | ---------------------------------------------------------- | | value | — | The releaseEmbed value | | titleAs | p | Element for the title — h2, h3, h4 or p | | showTracklist | true | Render the track list | | formatDate | ISO date | Formats the exact release date | | now | current date | Reference point for released vs upcoming — handy for tests | | resolveImageUrl | — | Build the cover URL yourself | | renderCover | — | Replace the <img> element | | injectStyles | true | Add the structural stylesheet | | labels | English | All visible texts, see below | | className | — | Extra class on the wrapper |

Labels

<Release
  value={block}
  labels={{
    types: {album: 'Album', ep: 'EP', single: 'Single', compilation: 'Compilation', live: 'Live'},
    released: (date) => `Released ${date}`,
    upcoming: (date) => `Out ${date}`,
    announced: (text) => `Announced for ${text}`,
    unknown: 'Release date to be announced',
    featuring: (artists) => `feat. ${artists.join(', ')}`,
    single: 'Single',
    unknownTrack: 'Title not announced yet',
    tracklist: 'Tracklist',
  }}
/>

Linking to the release page

An explicit URL in the Studio always wins. Without one, resolveHref builds the link — that is where your site's routing lives, since a plugin cannot know it:

import Link from 'next/link'
import {Release, slugOf} from 'sanity-plugin-sgntech-release/render'

;<Release
  value={block}
  resolveHref={(release) => `/releases/${slugOf(release)}`}
  renderLink={({href, className, children}) => (
    <Link href={href} className={className}>
      {children}
    </Link>
  )}
/>

Absolute http(s) links open in a new tab with rel="noreferrer noopener"; relative ones stay in the tab. Pass linkCover={false} to link only the title.

Styling

.sgn-release {
  --sgn-release-cover-width: 12rem;
  --sgn-release-gap: 1.25rem;
  --sgn-release-radius: 0;
  --sgn-release-breakpoint: 30rem; /* below this the cover moves above the facts */
  --sgn-release-meta-size: 0.875em;
  --sgn-release-meta-opacity: 0.75;
  --sgn-release-placeholder: color-mix(in srgb, currentColor 18%, transparent);
  --sgn-release-placeholder-height: 0.7em;
}

Class names: sgn-release, __head, __cover, __meta, __title, __artist, __facts, __fact, __status, __tracks, __track, __track-title, __featuring, __duration, __single, __placeholder, __link. The wrapper carries data-state (released, upcoming, announced, unknown) and data-type, so a release that is already out can look different without extra markup.

Accessibility

  • Placeholder bars are decorative; screen readers get the words instead ("Title not announced yet"), so a half-known tracklist is not read as a row of nothing.
  • The bar width is derived from the track number, not random — server and browser render the same thing, which keeps hydration quiet.
  • Alt text on the cover is required in the Studio and falls back to "«Title» by «Artist»".
  • The tracklist is an ordered list, so positions are announced.

Develop

npm install
npm test            # unit tests for status, tracks, cover and merging
npm run lint
npm run build
npm run link-watch  # build + publish to a local yalc repo for Studio testing

Built with @sanity/plugin-kit.

License

MIT © SGNTech