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

@dtfgangsheetapp/hydrogen

v1.0.2

Published

DTF Gang Sheet Builder for Shopify Hydrogen, Next.js Commerce, Remix, and any React-based headless storefront.

Downloads

348

Readme

@dtfgangsheetapp/hydrogen

React components for embedding the DTF Gang Sheet Builder in any headless Shopify storefront — Hydrogen, Next.js Commerce, Remix, Gatsby, or custom React apps.

Zero runtime dependencies except React. Inline styles, Shadow DOM-safe, SSR-compatible, fully typed.

Installation

npm install @dtfgangsheetapp/hydrogen
# or
pnpm add @dtfgangsheetapp/hydrogen
# or
yarn add @dtfgangsheetapp/hydrogen

Peer dependencies: react >= 17, react-dom >= 17.

Quick start

1. Wrap your app with GangSheetProvider

import { GangSheetProvider } from '@dtfgangsheetapp/hydrogen';

function App({ children }) {
  return (
    <GangSheetProvider
      config={{
        embedKey: process.env.NEXT_PUBLIC_DTFGSA_EMBED_KEY!,
        onOrderComplete: async (order) => {
          // Your cart/checkout integration
          const res = await fetch('/api/gangsheet/checkout', {
            method: 'POST',
            body: JSON.stringify(order),
          });
          const { invoiceUrl } = await res.json();
          return { redirectUrl: invoiceUrl };
        },
      }}
    >
      {children}
    </GangSheetProvider>
  );
}

2. Add the button to product pages

import { GangSheetButton } from '@dtfgangsheetapp/hydrogen';

function ProductPage({ product }) {
  return (
    <>
      <h1>{product.title}</h1>

      {/* ... your normal add-to-cart ... */}

      <GangSheetButton
        productId={product.id}
        variantId={product.variants[0]?.id}
        productHandle={product.handle}
      />
    </>
  );
}

That's it. Customer clicks the button, a modal opens with the builder, they design their gang sheet, and your onOrderComplete handler is called.


Get an embed key

  1. Sign up at builder.dtfgangsheetapp.com
  2. Dashboard → Integrations → Generate Embed Key
  3. Copy the ek_... key, save it as DTFGSA_EMBED_KEY in your env vars

Free tier includes 5 m² of gang sheets per month. Paid plans start at $9/month.


Complete guides


API reference

<GangSheetProvider>

Root context provider. Must wrap any components that use the builder.

<GangSheetProvider
  config={{
    embedKey: string,                    // required — get from dashboard
    apiUrl?: string,                     // default: 'https://api.dtfgangsheetapp.com'
    builderUrl?: string,                 // default: 'https://builder.dtfgangsheetapp.com'
    buttonText?: string,                 // default: 'Create a DTF Gang Sheet'
    buttonColor?: string,                // default: '#2563eb'
    buttonTextColor?: string,            // default: '#ffffff'
    mode?: 'modal' | 'page' | 'inline',  // default: 'modal'
    replaceCart?: boolean,               // default: false
    currency?: string,                   // default: 'USD'
    customer?: {
      email?: string,
      id?: string,
      name?: string,
    },
    onOrderComplete: (order) => Promise<{ redirectUrl?: string }>,  // your cart integration
    onClose?: () => void,
    onError?: (error: Error) => void,
  }}
  fetchRemoteConfig={true}               // fetch subscriber's preferences from backend
>
  {children}
</GangSheetProvider>

<GangSheetButton>

Renders a button that opens the builder modal when clicked.

<GangSheetButton
  text?="Custom button text"             // overrides provider default
  color?="#ff6600"                       // overrides provider default
  textColor?="#ffffff"                   // overrides provider default
  className?="my-custom-class"
  style?={{ marginTop: 12 }}
  productId?={product.id}                // passes to builder for context
  variantId?={variant.id}
  productHandle?={product.handle}

  // Render-prop for custom button UI:
  render?={({ onClick, text }) => (
    <button onClick={onClick} className="my-button">
      ✨ {text}
    </button>
  )}
/>

<GangSheetModal>

If you want programmatic control over when the modal opens, use this directly.

const [open, setOpen] = useState(false);

<button onClick={() => setOpen(true)}>Open manually</button>
<GangSheetModal
  open={open}
  onClose={() => setOpen(false)}
  productId={product.id}
/>

useGangSheetConfig()

Hook that returns the resolved config from the provider.

function MyComponent() {
  const config = useGangSheetConfig();
  return <div>Button text: {config.buttonText}</div>;
}

useHydrogenCart() (Hydrogen-specific subpath)

Returns an onOrderComplete handler suitable for Hydrogen stores.

import { useHydrogenCart } from '@dtfgangsheetapp/hydrogen/hydrogen';

const handleOrder = useHydrogenCart({
  shop: 'my-store.myshopify.com',
  mode: 'draft-order',      // recommended — creates a draft order and redirects
});

<GangSheetProvider config={{ embedKey, onOrderComplete: handleOrder }}>
  ...
</GangSheetProvider>

Draft-order mode (default): Calls our backend, which creates a Shopify draft order with the exact gang sheet price. Returns the invoice URL — customer gets redirected there to pay. Simplest + most reliable.

Cart-add mode: Adds a carrier variant to Hydrogen's cart with gang sheet attributes. Requires:

  1. A $0.01 carrier variant in your Shopify store (create via Admin API)
  2. A Shopify Function (delivery or discount) to override line totals based on the Calculated Price attribute

For most stores, draft-order mode is the right choice.

notifyOrder() (server-side)

After a Hydrogen/Next.js order is confirmed on your end, call this to mark the credit reservation as confirmed on our backend.

import { notifyOrder } from '@dtfgangsheetapp/hydrogen';

// In your order-confirmation webhook or API route:
await notifyOrder(embedKey, {
  orderId: order.orderId,         // gang sheet order ID (from the customer's design session)
  reservation: order.reservation, // reservation code
  platform: 'hydrogen',
  event: 'paid',
  externalOrderId: shopifyOrderId,
  total: order.price,
  currency: order.currency,
  customerEmail: customer.email,
});

How it works under the hood

  1. <GangSheetButton> renders a styled <button> that opens <GangSheetModal> on click.
  2. <GangSheetModal> renders an overlay with an iframe pointing to builder.dtfgangsheetapp.com/?embed=1&key=....
  3. Customer designs their gang sheet inside the iframe.
  4. When customer clicks "Add to cart" or "Checkout" in the builder, the iframe sends a postMessage to the parent window.
  5. The modal listens for messages with origin === builderUrl, parses the payload into a GangSheetOrder object, and calls your onOrderComplete handler.
  6. Your handler does whatever it needs — create a draft order, add to Hydrogen cart, etc. — and returns { redirectUrl }.
  7. The modal either redirects (if URL provided) or closes.

All origin validation is strict — postMessage events from any other origin are ignored. CSS is fully self-contained via inline styles. Modal is rendered via React portal into document.body.


SSR / hydration safety

All components are SSR-safe:

  • <GangSheetProvider> doesn't touch window during render; fetchEmbedConfig only runs in useEffect
  • <GangSheetModal> returns null if document is undefined (preventing SSR mismatch)
  • No hydration warnings — button renders identically on server and client

TypeScript

Full types exported. Every component, hook, and helper is typed.

import type {
  GangSheetConfig,
  GangSheetOrder,
  OrderCompleteResult,
  GangSheetButtonProps,
} from '@dtfgangsheetapp/hydrogen';

Customization

Custom button styling (bring your own CSS)

<GangSheetButton
  render={({ onClick, text }) => (
    <button onClick={onClick} className="tw-btn tw-btn-primary tw-w-full">
      {text}
    </button>
  )}
/>

Custom modal (fully replace)

Don't use <GangSheetButton> — instead use <GangSheetModal> directly or build your own iframe host. See src/components/GangSheetModal.tsx for reference.

Translations

<GangSheetProvider
  config={{
    embedKey,
    buttonText: t('gangsheet.button'),  // your i18n library
  }}
>
  <GangSheetButton />
</GangSheetProvider>

Browser support

  • Chrome/Edge 90+
  • Firefox 88+
  • Safari 14+
  • iOS Safari 14+
  • Android Chrome 90+

Troubleshooting

"useGangSheetConfig must be called inside "

The button/hook is being used outside the provider. Make sure <GangSheetProvider> wraps the component tree that contains <GangSheetButton>.

Modal doesn't close after order

Make sure your onOrderComplete handler returns (resolves). If it throws, the modal stays open showing an error.

Button appears but clicking does nothing

Check browser console for CORS errors. The builder iframe must load from builder.dtfgangsheetapp.com — if you've proxied or firewalled it, the iframe won't load.

"Invalid embed key" errors

Visit the dashboard to regenerate your key, or verify the key is set correctly in env vars.


License

MIT © DTFGSA Inc. See LICENSE.


Support