@best-bundles/bundle-ui
v0.0.32
Published
Bundle Builder UI components and hooks for Shopify storefronts.
Readme
@best-bundles/bundle-ui
Bundle Builder UI components and hooks for Shopify storefronts.
Install
npm install @best-bundles/bundle-ui lucide-reactPeer deps: react, react-dom, and lucide-react.
Hydrogen setup
1) Create a cart adapter (passthrough to Hydrogen cart handlers)
Create a small adapter that forwards Bundle Builder line changes to your Hydrogen cart. Adjust the cart UI open/close helpers to match your storefront.
app/lib/bundleCartAdapter.tsx
import { useMemo } from "react";
import type { BundleCartAdapter } from "@best-bundles/bundle-ui";
import { useCart } from "@shopify/hydrogen";
export function useHydrogenBundleCartAdapter(): BundleCartAdapter {
const { linesAdd, linesUpdate, linesRemove, openCart, closeCart } = useCart();
return useMemo(
() => ({
async linesAdd(lines) {
await linesAdd(lines);
},
async linesUpdate(lines) {
await linesUpdate(lines);
},
async linesRemove(lineIds) {
await linesRemove(lineIds);
},
openCartUI() {
openCart?.();
},
closeCartUI() {
closeCart?.();
},
}),
[linesAdd, linesUpdate, linesRemove, openCart, closeCart],
);
}If your Hydrogen setup uses @shopify/hydrogen-react, import useCart from there instead.
2) Wrap the app in root.tsx with BundleProvider
Place the provider inside your Hydrogen CartProvider so useCart() is available.
import { BundleProvider } from "@best-bundles/bundle-ui";
import { useHydrogenBundleCartAdapter } from "~/lib/bundleCartAdapter";
export default function App() {
const { env } = useLoaderData<typeof loader>();
const cartAdapter = useHydrogenBundleCartAdapter();
return (
<CartProvider>
<BundleProvider
apiBaseUrl={env.BEST_BUNDLES_API_BASE_URL}
shop={env.PUBLIC_STORE_DOMAIN}
cartAdapter={cartAdapter}
>
<Layout>
<Outlet />
</Layout>
</BundleProvider>
</CartProvider>
);
}Required props:
apiBaseUrl: Base URL of the Best Bundles app (used for config + analytics).shop: Your shop domain (used to fetch the bundle config).cartAdapter: The passthrough adapter from step 1.
Optional props:
configHandle: Defaults to"default".analyticsEndpoint: Defaults to${apiBaseUrl}/api/public/bundle-analytics.
3) Mount the drawer UI in Layout.tsx
Render the drawer once in your layout so it can be opened anywhere.
import { BundleBuilderDrawer, BundleButton } from "@best-bundles/bundle-ui";
export function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<header>
<BundleButton>Build a bundle</BundleButton>
</header>
{children}
<BundleBuilderDrawer />
</>
);
}useBundleBuilder hook
The hook exposes drawer state, selections, and submit actions. It must be used
inside BundleProvider.
import { useBundleBuilder } from "@best-bundles/bundle-ui";
export function CustomBundleTrigger() {
const { toggle, canSubmit, bundleSize, minRequired } = useBundleBuilder();
return (
<button type="button" onClick={toggle} disabled={!canSubmit && bundleSize < minRequired}>
Build a bundle
</button>
);
}Key fields include:
isOpen,open,close,toggleloading,submitting,errorconfig,eligibleVariantsselections,selectionOrder,setQuantity,clearSelectionsbundleSize,minRequired,canSubmit,submit
Bundle cart adapter shape
If you need to build your own adapter, implement BundleCartAdapter:
export type BundleCartAdapter = {
linesAdd(lines: Array<{
merchandiseId: string;
quantity: number;
attributes?: { key: string; value: string }[];
}>): Promise<void> | void;
linesUpdate?(lines: Array<{ id: string; quantity: number; attributes?: { key: string; value: string }[] }>): Promise<void> | void;
linesRemove?(lineIds: string[]): Promise<void> | void;
openCartUI?(): void;
closeCartUI?(): void;
};For Liquid storefronts, createLiquidCartAdapter() is available.
