@develemit/storefront 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +265 -2
  2. package/dist/cart/cart-provider.d.ts +24 -0
  3. package/dist/cart/cart-reducer.d.ts +31 -0
  4. package/dist/cart/cart-storage.d.ts +6 -0
  5. package/dist/client/commerce-client.d.ts +13 -0
  6. package/dist/client/errors.d.ts +12 -0
  7. package/dist/client/index.d.ts +2 -0
  8. package/dist/client/index.js +64 -0
  9. package/dist/client/index.js.map +1 -0
  10. package/dist/components/add-to-cart-button/add-to-cart-button.d.ts +14 -0
  11. package/dist/components/availability-badge/availability-badge.d.ts +15 -0
  12. package/dist/components/cart-button/cart-button.d.ts +9 -0
  13. package/dist/components/cart-drawer/cart-drawer.d.ts +18 -0
  14. package/dist/components/cart-drawer/use-drawer-presence.d.ts +5 -0
  15. package/dist/components/cart-line-item/cart-line-item.d.ts +11 -0
  16. package/dist/components/cart-summary/cart-summary.d.ts +10 -0
  17. package/dist/components/category-filter/category-filter.d.ts +11 -0
  18. package/dist/components/fixtures.d.ts +2 -0
  19. package/dist/components/price-tag/price-tag.d.ts +7 -0
  20. package/dist/components/product-card/product-card.d.ts +9 -0
  21. package/dist/components/product-gallery/product-gallery.d.ts +6 -0
  22. package/dist/components/product-grid/product-grid.d.ts +10 -0
  23. package/dist/components/render-with-cart.d.ts +3 -0
  24. package/dist/format/index.d.ts +2 -0
  25. package/dist/format/index.js +1 -0
  26. package/dist/index.d.ts +22 -0
  27. package/dist/index.js +686 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/lib/format-price.d.ts +1 -0
  30. package/dist/lib/parse-duration.d.ts +1 -0
  31. package/dist/lib/provider.d.ts +28 -0
  32. package/dist/lib/theme.d.ts +15 -0
  33. package/dist/sort-for-display-Cj6GMeCY.js +15 -0
  34. package/dist/sort-for-display-Cj6GMeCY.js.map +1 -0
  35. package/dist/styles.css +476 -0
  36. package/dist/views/featured-products.d.ts +8 -0
  37. package/dist/views/product-detail-view.d.ts +13 -0
  38. package/dist/views/shop-view.d.ts +21 -0
  39. package/dist/views/sort-for-display.d.ts +2 -0
  40. package/package.json +46 -3
package/README.md CHANGED
@@ -1,3 +1,266 @@
1
- # Temporary Holding Version
1
+ # @develemit/storefront
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Brand-agnostic storefront UI and commerce logic, built so it can be extracted
4
+ and published as a standalone package.
5
+
6
+ ## Boundary rules
7
+
8
+ - No client branding, copy, colors, or product data in this package.
9
+ The consuming app supplies those through props, theme tokens, and data adapters.
10
+ - No imports from `apps/*` or other app-specific packages.
11
+ - React, React DOM, and Next are peer dependencies, never bundled.
12
+
13
+ ## Commands
14
+
15
+ - `pnpm exec nx build storefront` — library build to `packages/storefront/dist`
16
+ - `pnpm exec nx test storefront`
17
+
18
+ ## Theme contract
19
+
20
+ Import `@develemit/storefront/styles.css` for neutral defaults, then override
21
+ the variables in the host app (or pass a `StorefrontTheme` through
22
+ `themeToCssVars()`).
23
+
24
+ | Variable | Purpose |
25
+ | ----------------------------- | ------------------------------------------------- |
26
+ | `--sf-color-bg` | Page background |
27
+ | `--sf-color-surface` | Cards and raised surfaces |
28
+ | `--sf-color-fg` | Primary text |
29
+ | `--sf-color-muted` | Secondary text |
30
+ | `--sf-color-accent` | Buttons and links |
31
+ | `--sf-color-accent-fg` | Text on accent backgrounds |
32
+ | `--sf-color-border` | Borders and dividers |
33
+ | `--sf-color-sold` | Sold / unavailable treatment |
34
+ | `--sf-radius` | Corner radius |
35
+ | `--sf-font-display` | Headings font stack |
36
+ | `--sf-font-body` | Body font stack |
37
+ | `--sf-space-unit` | Base spacing unit |
38
+ | `--sf-motion-duration` | Drawer motion (default `0ms`) |
39
+ | `--sf-color-focus` | Gallery focus ring (default `currentColor`) |
40
+ | `--sf-card-hover-lift` | Card hover lift (default `0px`) |
41
+ | `--sf-card-hover-shadow` | Card hover shadow (default `none`) |
42
+ | `--sf-card-image-hover-scale` | Card image hover zoom (default `1`) |
43
+ | `--sf-sold-opacity` | Sold card image opacity (default `0.55`) |
44
+ | `--sf-sold-filter` | Sold card image filter (default `grayscale(0.6)`) |
45
+ | `--sf-grid-columns-mobile` | Grid columns below 40rem (default `1`) |
46
+
47
+ ## Components
48
+
49
+ `PriceTag`, `AvailabilityBadge`, `ProductCard`, `ProductGrid`,
50
+ `CategoryFilter`, and `ProductGallery` are styled only through the `--sf-*`
51
+ variables. Wrap them in `StorefrontProvider` to supply framework adapters;
52
+ without it they fall back to plain `<a>` and `<img>`.
53
+
54
+ Example Next.js host setup (host code, not part of this package):
55
+
56
+ ```tsx
57
+ import Link from 'next/link';
58
+ import Image from 'next/image';
59
+ import { StorefrontProvider } from '@develemit/storefront';
60
+ import '@develemit/storefront/styles.css';
61
+
62
+ export function Providers({ children }: { children: React.ReactNode }) {
63
+ return (
64
+ <StorefrontProvider
65
+ locale="en-US"
66
+ renderLink={({ href, children, className }) => (
67
+ <Link href={href} className={className}>
68
+ {children}
69
+ </Link>
70
+ )}
71
+ renderImage={({ src, alt, sizes, className }) => (
72
+ <Image src={src} alt={alt} sizes={sizes} className={className} fill />
73
+ )}
74
+ hrefs={{
75
+ product: (slug) => `/shop/${slug}`,
76
+ category: (slug) => (slug ? `/shop?category=${slug}` : '/shop'),
77
+ }}
78
+ >
79
+ {children}
80
+ </StorefrontProvider>
81
+ );
82
+ }
83
+ ```
84
+
85
+ `AvailabilityBadge` and the components that embed it take a `labels` prop to
86
+ override the default copy ("One of a kind", "Sold", "On hold", "Available").
87
+ `CategoryFilter` renders buttons when `onSelect` is passed (controlled mode) and
88
+ links otherwise.
89
+
90
+ ## Server-safe helpers
91
+
92
+ The main entry carries a `"use client"` directive, so in React Server
93
+ Components its exports are client references and cannot be called. Pure helpers
94
+ live in a directive-free entry:
95
+
96
+ ```ts
97
+ import { formatPrice, sortForDisplay } from '@develemit/storefront/format';
98
+
99
+ formatPrice(2500, 'USD'); // "$25.00"
100
+ ```
101
+
102
+ Both are still exported from the main entry for client components.
103
+
104
+ ## API client
105
+
106
+ `createCommerceClient` lives in a separate, React-free entry so server code can
107
+ use it without pulling in the UI:
108
+
109
+ ```ts
110
+ import { createCommerceClient } from '@develemit/storefront/client';
111
+
112
+ const client = createCommerceClient({ baseUrl: 'https://example.com/api' });
113
+ const { items, nextCursor } = await client.listProducts({
114
+ category: 'rings',
115
+ limit: 24, // default 48, max 100
116
+ // cursor: nextCursor from the previous page
117
+ });
118
+ await client.getProduct('sample-piece'); // throws NotFoundError on 404
119
+ await client.listCategories();
120
+ await client.createCheckout([{ productId: 'p1', quantity: 1 }]); // { url }
121
+ await client.getOrderBySession('cs_test_123'); // order summary
122
+ ```
123
+
124
+ `baseUrl` is where the commerce server plugins are mounted; the client appends
125
+ `/catalog/...`, `/checkout`, and `/orders/...`. Responses are parsed with the
126
+ contract schemas, so a drifting server fails loudly. Other non-2xx responses
127
+ throw `CommerceApiError`.
128
+
129
+ `createCheckout` throws `CheckoutConflictError` (a 409) when some items are
130
+ sold or held by another buyer; its `productIds` lists them so the cart can drop
131
+ them. Send only ids and quantities: the server prices every line itself.
132
+ Redirect the shopper to the returned `url`.
133
+
134
+ ## Images in production
135
+
136
+ The default image adapter is a plain `<img loading="lazy" decoding="async">`
137
+ (`priority` switches to `loading="eager"` and `fetchpriority="high"`). It does
138
+ not resize. Production hosts **must** supply a resizing `renderImage` (such as
139
+ `next/image`) or serve through a resizing CDN; otherwise every card downloads
140
+ the full-size original, and admin uploads can be up to 8MB.
141
+
142
+ Pass accurate `sizes` so the adapter picks a small variant. For `ProductGrid`,
143
+ the sizes should match your column layout, for example
144
+ `(min-width: 1024px) 25vw, (min-width: 640px) 50vw, 100vw`.
145
+
146
+ ## Views
147
+
148
+ `ShopView`, `ProductDetailView`, and `FeaturedProducts` take plain data plus
149
+ slots (`header`, `intro`, `emptyState`, `actions`, `afterDetails`).
150
+ `ShopView` lists sold items last (`sortForDisplay`).
151
+
152
+ ### Render a shop in a Next app
153
+
154
+ ```tsx
155
+ // app/shop/page.tsx (server component)
156
+ import { createCommerceClient } from '@develemit/storefront/client';
157
+ import { ShopView } from '@develemit/storefront';
158
+
159
+ const client = createCommerceClient({ baseUrl: process.env.COMMERCE_API_URL! });
160
+
161
+ export default async function ShopPage({
162
+ searchParams,
163
+ }: {
164
+ searchParams: Promise<{ category?: string; cursor?: string }>;
165
+ }) {
166
+ const { category, cursor } = await searchParams;
167
+ const [page, categories] = await Promise.all([
168
+ client.listProducts({ category, cursor }),
169
+ client.listCategories(),
170
+ ]);
171
+ return (
172
+ <ShopView
173
+ products={page.items}
174
+ nextHref={
175
+ page.nextCursor
176
+ ? `/shop?cursor=${encodeURIComponent(page.nextCursor)}`
177
+ : undefined
178
+ }
179
+ categories={categories}
180
+ activeCategory={category}
181
+ slots={{ header: <h1>Shop</h1> }}
182
+ />
183
+ );
184
+ }
185
+ ```
186
+
187
+ ```tsx
188
+ // app/shop/[slug]/page.tsx
189
+ import { notFound } from 'next/navigation';
190
+ import {
191
+ createCommerceClient,
192
+ NotFoundError,
193
+ } from '@develemit/storefront/client';
194
+ import { ProductDetailView } from '@develemit/storefront';
195
+
196
+ export default async function ProductPage({
197
+ params,
198
+ }: {
199
+ params: Promise<{ slug: string }>;
200
+ }) {
201
+ const { slug } = await params;
202
+ try {
203
+ const product = await client.getProduct(slug);
204
+ return <ProductDetailView product={product} />;
205
+ } catch (e) {
206
+ if (e instanceof NotFoundError) notFound();
207
+ throw e;
208
+ }
209
+ }
210
+ ```
211
+
212
+ ## Cart
213
+
214
+ Wrap the app in `CartProvider` (inside `StorefrontProvider`). State is a
215
+ snapshot of `CartLine`s; prices and availability are re-verified server-side
216
+ at checkout. Persistence goes through a `CartStorage` (`load`/`save`); the
217
+ default is `localStorageCartStorage(key)`, which hydrates after mount and never
218
+ touches `window` during render.
219
+
220
+ ```tsx
221
+ <CartProvider>
222
+ <CartButton />
223
+ <CartDrawer summary={{ onCheckout, checkoutLabel: 'Checkout' }} />
224
+ </CartProvider>
225
+
226
+ <ProductDetailView
227
+ product={product}
228
+ slots={{ actions: <AddToCartButton product={product} /> }}
229
+ />
230
+ ```
231
+
232
+ `useCart()` exposes `add` (returns `{ added, capped, reason? }`), `remove`,
233
+ `setQuantity`, `clear`, `open`/`close`, and the derived `itemCount` and
234
+ `subtotalCents`. A product's `quantity` is the line's `maxQuantity`, so
235
+ one-of-a-kind items can only be added once. Every label is overridable
236
+ through a `labels` prop.
237
+
238
+ ### Polish hooks
239
+
240
+ - `CartDrawer` `slots={{ empty, summaryNote }}`: `empty` replaces the
241
+ `labels.empty` text (for example with a link back to the shop); `summaryNote`
242
+ renders under the subtotal and Checkout button. An empty cart shows no
243
+ summary or Checkout button. Close and Remove are at least 44×44px.
244
+ - `CartButton` `icon` (rendered before the label), `labelHidden` (visually
245
+ hides the label, keeping an accessible name like "Cart, 2 items") and
246
+ `hideZeroCount` (omits the count at 0; the accessible name becomes
247
+ "Cart, empty").
248
+ - `--sf-motion-duration` (default `0ms`, no motion) drives the drawer's
249
+ slide-in and the backdrop's fade, in and out. It is forced to `0ms` under
250
+ `prefers-reduced-motion: reduce`. Example: `--sf-motion-duration: 200ms`.
251
+
252
+ ### Catalog presentation hooks
253
+
254
+ - Card hover: `--sf-card-hover-lift`, `--sf-card-hover-shadow` and
255
+ `--sf-card-image-hover-scale` apply on `:hover` and `:focus-within`, animated
256
+ with `--sf-motion-duration` (off under `prefers-reduced-motion: reduce`).
257
+ - Sold treatment: `--sf-sold-opacity` and `--sf-sold-filter` style the sold
258
+ card image.
259
+ - `--sf-grid-columns-mobile` sets the column count below 40rem.
260
+ - `CategoryFilter` `formatLabel(category)` and `sortCategories(a, b)` change
261
+ the label text and order (the input array is not mutated); `allLabel` renames
262
+ the All item. `ShopView` forwards them as `formatCategoryLabel`,
263
+ `sortCategories` and `allLabel`.
264
+ - `ShopView`, `FeaturedProducts` and `ProductDetailView` accept
265
+ `labels?: Partial<AvailabilityLabels>` for the availability badge.
266
+ - The gallery focus ring uses `--sf-color-focus`.
@@ -0,0 +1,24 @@
1
+ import { ReactNode } from 'react';
2
+ import { CartLine } from '@develemit/commerce-contracts';
3
+ import { AddResult } from './cart-reducer';
4
+ import { CartStorage } from './cart-storage';
5
+ export interface CartContextValue {
6
+ lines: CartLine[];
7
+ itemCount: number;
8
+ subtotalCents: number;
9
+ currency: string | null;
10
+ hydrated: boolean;
11
+ isOpen: boolean;
12
+ open: () => void;
13
+ close: () => void;
14
+ add: (line: CartLine) => AddResult;
15
+ remove: (productId: string) => void;
16
+ setQuantity: (productId: string, quantity: number) => void;
17
+ clear: () => void;
18
+ }
19
+ export interface CartProviderProps {
20
+ storage?: CartStorage;
21
+ children: ReactNode;
22
+ }
23
+ export declare function CartProvider({ storage, children, }: CartProviderProps): import('react').JSX.Element;
24
+ export declare function useCart(): CartContextValue;
@@ -0,0 +1,31 @@
1
+ import { CartLine } from '@develemit/commerce-contracts';
2
+ export interface CartState {
3
+ lines: CartLine[];
4
+ }
5
+ export type CartAction = {
6
+ type: 'add';
7
+ line: CartLine;
8
+ } | {
9
+ type: 'remove';
10
+ productId: string;
11
+ } | {
12
+ type: 'setQuantity';
13
+ productId: string;
14
+ quantity: number;
15
+ } | {
16
+ type: 'clear';
17
+ } | {
18
+ type: 'hydrate';
19
+ state: CartState;
20
+ };
21
+ export interface AddResult {
22
+ state: CartState;
23
+ added: boolean;
24
+ capped: boolean;
25
+ reason?: 'currency-mismatch';
26
+ }
27
+ export declare const emptyCart: CartState;
28
+ export declare function addLine(state: CartState, line: CartLine): AddResult;
29
+ export declare function cartReducer(state: CartState, action: CartAction): CartState;
30
+ export declare function subtotalCents(state: CartState): number;
31
+ export declare function itemCount(state: CartState): number;
@@ -0,0 +1,6 @@
1
+ import { CartState } from './cart-reducer';
2
+ export interface CartStorage {
3
+ load: () => CartState | Promise<CartState>;
4
+ save: (state: CartState) => void | Promise<void>;
5
+ }
6
+ export declare function localStorageCartStorage(key?: string): CartStorage;
@@ -0,0 +1,13 @@
1
+ import { Category, CheckoutRequest, CheckoutResponse, OrderSummary, ProductCardPage, Product, ProductListQuery } from '@develemit/commerce-contracts';
2
+ export interface CommerceClientOptions {
3
+ baseUrl: string;
4
+ fetch?: typeof fetch;
5
+ }
6
+ export interface CommerceClient {
7
+ listProducts(query?: ProductListQuery): Promise<ProductCardPage>;
8
+ getProduct(slug: string): Promise<Product>;
9
+ listCategories(): Promise<Category[]>;
10
+ createCheckout(items: CheckoutRequest['items']): Promise<CheckoutResponse>;
11
+ getOrderBySession(sessionId: string): Promise<OrderSummary>;
12
+ }
13
+ export declare function createCommerceClient({ baseUrl, fetch: fetchImpl, }: CommerceClientOptions): CommerceClient;
@@ -0,0 +1,12 @@
1
+ export declare class CommerceApiError extends Error {
2
+ readonly status: number;
3
+ readonly url: string;
4
+ constructor(status: number, url: string, message?: string);
5
+ }
6
+ export declare class NotFoundError extends CommerceApiError {
7
+ constructor(url: string);
8
+ }
9
+ export declare class CheckoutConflictError extends CommerceApiError {
10
+ readonly productIds: string[];
11
+ constructor(url: string, productIds: string[]);
12
+ }
@@ -0,0 +1,2 @@
1
+ export * from './commerce-client';
2
+ export * from './errors';
@@ -0,0 +1,64 @@
1
+ import { categorySchema as e, checkoutConflictSchema as t, checkoutResponseSchema as n, orderSummarySchema as r, productCardPageSchema as i, productSchema as a } from "@develemit/commerce-contracts";
2
+ // src/client/errors.ts
3
+ var o = class extends Error {
4
+ status;
5
+ url;
6
+ constructor(e, t, n) {
7
+ super(n ?? `Commerce API request failed (${e}): ${t}`), this.status = e, this.url = t, this.name = "CommerceApiError";
8
+ }
9
+ }, s = class extends o {
10
+ constructor(e) {
11
+ super(404, e, `Not found: ${e}`), this.name = "NotFoundError";
12
+ }
13
+ }, c = class extends o {
14
+ productIds;
15
+ constructor(e, t) {
16
+ super(409, e, `Some items are no longer available: ${t.join(", ")}`), this.productIds = t, this.name = "CheckoutConflictError";
17
+ }
18
+ };
19
+ // src/client/commerce-client.ts
20
+ function l({ baseUrl: l, fetch: u = (...e) => globalThis.fetch(...e) }) {
21
+ let d = l.replace(/\/+$/, "");
22
+ async function f(e, t, n) {
23
+ if (t.status === 404) throw new s(e);
24
+ if (!t.ok) throw new o(t.status, e);
25
+ return n.parse(await t.json());
26
+ }
27
+ async function p(e, t) {
28
+ let n = `${d}${e}`;
29
+ return f(n, await u(n, { headers: { accept: "application/json" } }), t);
30
+ }
31
+ async function m(e) {
32
+ let r = `${d}/checkout`, i = await u(r, {
33
+ method: "POST",
34
+ headers: {
35
+ accept: "application/json",
36
+ "content-type": "application/json"
37
+ },
38
+ body: JSON.stringify({ items: e })
39
+ });
40
+ if (i.status === 409) throw new c(r, t.parse(await i.json()).unavailableProductIds);
41
+ return f(r, i, n);
42
+ }
43
+ return {
44
+ async listProducts(e = {}) {
45
+ let t = new URLSearchParams();
46
+ e.category && t.set("category", e.category), e.featured !== void 0 && t.set("featured", String(e.featured)), e.limit !== void 0 && t.set("limit", String(e.limit)), e.cursor && t.set("cursor", e.cursor);
47
+ let n = t.toString();
48
+ return p(`/catalog/products${n ? `?${n}` : ""}`, i);
49
+ },
50
+ getProduct(e) {
51
+ return p(`/catalog/products/${encodeURIComponent(e)}`, a);
52
+ },
53
+ listCategories() {
54
+ return p("/catalog/categories", e.array());
55
+ },
56
+ createCheckout: m,
57
+ getOrderBySession(e) {
58
+ return p(`/orders/session/${encodeURIComponent(e)}`, r);
59
+ }
60
+ };
61
+ }
62
+ export { c as CheckoutConflictError, o as CommerceApiError, s as NotFoundError, l as createCommerceClient };
63
+
64
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["CommerceApiError","message","status","url","NotFoundError","CheckoutConflictError","productIds","createCommerceClient","fetchImpl","args","root","baseUrl","readJson","res","schema","get","path","createCheckout","checkoutConflictSchema","checkoutResponseSchema","params","query","qs","productCardPageSchema","slug","productSchema","categorySchema","sessionId","orderSummarySchema"],"sources":["../../src/client/errors.ts","../../src/client/commerce-client.ts"],"sourcesContent":["export class CommerceApiError extends Error {\n constructor(\n readonly status: number,\n readonly url: string,\n message?: string,\n ) {\n super(message ?? `Commerce API request failed (${status}): ${url}`);\n this.name = 'CommerceApiError';\n }\n}\n\nexport class NotFoundError extends CommerceApiError {\n constructor(url: string) {\n super(404, url, `Not found: ${url}`);\n this.name = 'NotFoundError';\n }\n}\n\nexport class CheckoutConflictError extends CommerceApiError {\n constructor(\n url: string,\n readonly productIds: string[],\n ) {\n super(\n 409,\n url,\n `Some items are no longer available: ${productIds.join(', ')}`,\n );\n this.name = 'CheckoutConflictError';\n }\n}\n","import {\n categorySchema,\n checkoutConflictSchema,\n checkoutResponseSchema,\n orderSummarySchema,\n productCardPageSchema,\n productSchema,\n type Category,\n type CheckoutRequest,\n type CheckoutResponse,\n type OrderSummary,\n type ProductCardPage,\n type Product,\n type ProductListQuery,\n} from '@develemit/commerce-contracts';\nimport {\n CheckoutConflictError,\n CommerceApiError,\n NotFoundError,\n} from './errors';\n\nexport interface CommerceClientOptions {\n baseUrl: string;\n fetch?: typeof fetch;\n}\n\nexport interface CommerceClient {\n listProducts(query?: ProductListQuery): Promise<ProductCardPage>;\n getProduct(slug: string): Promise<Product>;\n listCategories(): Promise<Category[]>;\n createCheckout(items: CheckoutRequest['items']): Promise<CheckoutResponse>;\n getOrderBySession(sessionId: string): Promise<OrderSummary>;\n}\n\ntype Schema<T> = { parse(data: unknown): T };\n\nexport function createCommerceClient({\n baseUrl,\n fetch: fetchImpl = (...args) => globalThis.fetch(...args),\n}: CommerceClientOptions): CommerceClient {\n const root = baseUrl.replace(/\\/+$/, '');\n\n async function readJson<T>(\n url: string,\n res: Response,\n schema: Schema<T>,\n ): Promise<T> {\n if (res.status === 404) throw new NotFoundError(url);\n if (!res.ok) throw new CommerceApiError(res.status, url);\n return schema.parse(await res.json());\n }\n\n async function get<T>(path: string, schema: Schema<T>): Promise<T> {\n const url = `${root}${path}`;\n const res = await fetchImpl(url, {\n headers: { accept: 'application/json' },\n });\n return readJson(url, res, schema);\n }\n\n async function createCheckout(\n items: CheckoutRequest['items'],\n ): Promise<CheckoutResponse> {\n const url = `${root}/checkout`;\n const res = await fetchImpl(url, {\n method: 'POST',\n headers: {\n accept: 'application/json',\n 'content-type': 'application/json',\n },\n body: JSON.stringify({ items }),\n });\n if (res.status === 409) {\n const conflict = checkoutConflictSchema.parse(await res.json());\n throw new CheckoutConflictError(url, conflict.unavailableProductIds);\n }\n return readJson(url, res, checkoutResponseSchema);\n }\n\n return {\n async listProducts(query = {}) {\n const params = new URLSearchParams();\n if (query.category) params.set('category', query.category);\n if (query.featured !== undefined) {\n params.set('featured', String(query.featured));\n }\n if (query.limit !== undefined) params.set('limit', String(query.limit));\n if (query.cursor) params.set('cursor', query.cursor);\n const qs = params.toString();\n return get(\n `/catalog/products${qs ? `?${qs}` : ''}`,\n productCardPageSchema,\n );\n },\n getProduct(slug) {\n return get(\n `/catalog/products/${encodeURIComponent(slug)}`,\n productSchema,\n );\n },\n listCategories() {\n return get('/catalog/categories', categorySchema.array());\n },\n createCheckout,\n getOrderBySession(sessionId) {\n return get(\n `/orders/session/${encodeURIComponent(sessionId)}`,\n orderSummarySchema,\n );\n },\n };\n}\n"],"mappings":";;AAAA,IAAaA,IAAb,cAAsC,MAAM;CAE/B;CACA;CAFX,YACE,GACA,GACA,GACA;EAEA,AADA,MAAMC,KAAW,gCAAgCC,EAAO,KAAKC,GAAK,GAJzD,KAAA,SAAAD,GACA,KAAA,MAAAC,GAIT,KAAK,OAAO;CACd;AACF,GAEaC,IAAb,cAAmCJ,EAAiB;CAClD,YAAY,GAAa;EAEvB,AADA,MAAM,KAAKG,GAAK,cAAcA,GAAK,GACnC,KAAK,OAAO;CACd;AACF,GAEaE,IAAb,cAA2CL,EAAiB;CAG/C;CAFX,YACE,GACA,GACA;EAMA,AALA,MACE,KACAG,GACA,uCAAuCG,EAAW,KAAK,IAAI,GAC7D,GANS,KAAA,aAAAA,GAOT,KAAK,OAAO;CACd;AACF;;ACMA,SAAgBC,EAAqB,EACnC,YACA,OAAOC,KAAa,GAAGC,MAAS,WAAW,MAAM,GAAGA,CAAI,KAChB;CACxC,IAAMC,IAAOC,EAAQ,QAAQ,QAAQ,EAAE;CAEvC,eAAeC,EACb,GACA,GACA,GACY;EACZ,IAAIC,EAAI,WAAW,KAAK,MAAM,IAAIT,EAAcD,CAAG;EACnD,IAAI,CAACU,EAAI,IAAI,MAAM,IAAIb,EAAiBa,EAAI,QAAQV,CAAG;EACvD,OAAOW,EAAO,MAAM,MAAMD,EAAI,KAAK,CAAC;CACtC;CAEA,eAAeE,EAAO,GAAc,GAA+B;EACjE,IAAMZ,IAAM,GAAGO,IAAOM;EAItB,OAAOJ,EAAST,GAAK,MAHHK,EAAUL,GAAK,EAC/B,SAAS,EAAE,QAAQ,mBAAmB,EACxC,CAAC,GACyBW,CAAM;CAClC;CAEA,eAAeG,EACb,GAC2B;EAC3B,IAAMd,IAAM,GAAGO,EAAK,YACdG,IAAM,MAAML,EAAUL,GAAK;GAC/B,QAAQ;GACR,SAAS;IACP,QAAQ;IACR,gBAAgB;GAClB;GACA,MAAM,KAAK,UAAU,EAAE,SAAM,CAAC;EAChC,CAAC;EACD,IAAIU,EAAI,WAAW,KAEjB,MAAM,IAAIR,EAAsBF,GADfe,EAAuB,MAAM,MAAML,EAAI,KAAK,CACxB,CAAA,CAAS,qBAAqB;EAErE,OAAOD,EAAST,GAAKU,GAAKM,CAAsB;CAClD;CAEA,OAAO;EACL,MAAM,aAAa,IAAQ,CAAC,GAAG;GAC7B,IAAMC,IAAS,IAAI,gBAAgB;GAMnC,AALIC,EAAM,YAAUD,EAAO,IAAI,YAAYC,EAAM,QAAQ,GACrDA,EAAM,aAAa,KAAA,KACrBD,EAAO,IAAI,YAAY,OAAOC,EAAM,QAAQ,CAAC,GAE3CA,EAAM,UAAU,KAAA,KAAWD,EAAO,IAAI,SAAS,OAAOC,EAAM,KAAK,CAAC,GAClEA,EAAM,UAAQD,EAAO,IAAI,UAAUC,EAAM,MAAM;GACnD,IAAMC,IAAKF,EAAO,SAAS;GAC3B,OAAOL,EACL,oBAAoBO,IAAK,IAAIA,MAAO,MACpCC,CACF;EACF;EACA,WAAW,GAAM;GACf,OAAOR,EACL,qBAAqB,mBAAmBS,CAAI,KAC5CC,CACF;EACF;EACA,iBAAiB;GACf,OAAOV,EAAI,uBAAuBW,EAAe,MAAM,CAAC;EAC1D;EACA;EACA,kBAAkB,GAAW;GAC3B,OAAOX,EACL,mBAAmB,mBAAmBY,CAAS,KAC/CC,CACF;EACF;CACF;AACF"}
@@ -0,0 +1,14 @@
1
+ import { Product } from '@develemit/commerce-contracts';
2
+ export interface AddToCartLabels {
3
+ add: string;
4
+ inCart: string;
5
+ unavailable: string;
6
+ }
7
+ export declare const defaultAddToCartLabels: AddToCartLabels;
8
+ export interface AddToCartButtonProps {
9
+ product: Product;
10
+ labels?: Partial<AddToCartLabels>;
11
+ openOnAdd?: boolean;
12
+ className?: string;
13
+ }
14
+ export declare function AddToCartButton({ product, labels, openOnAdd, className, }: AddToCartButtonProps): import('react').JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { ProductAvailability } from '@develemit/commerce-contracts';
2
+ export interface AvailabilityLabels {
3
+ oneOfAKind: string;
4
+ sold: string;
5
+ reserved: string;
6
+ available: string;
7
+ }
8
+ export declare const defaultAvailabilityLabels: AvailabilityLabels;
9
+ export interface AvailabilityBadgeProps {
10
+ availability: ProductAvailability;
11
+ quantity: number;
12
+ labels?: Partial<AvailabilityLabels>;
13
+ className?: string;
14
+ }
15
+ export declare function AvailabilityBadge({ availability, quantity, labels, className, }: AvailabilityBadgeProps): import('react').JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { ReactNode } from 'react';
2
+ export interface CartButtonProps {
3
+ label?: string;
4
+ className?: string;
5
+ icon?: ReactNode;
6
+ labelHidden?: boolean;
7
+ hideZeroCount?: boolean;
8
+ }
9
+ export declare function CartButton({ label, className, icon, labelHidden, hideZeroCount, }: CartButtonProps): import('react').JSX.Element;
@@ -0,0 +1,18 @@
1
+ import { ReactNode } from 'react';
2
+ import { CartSummaryProps } from '../cart-summary/cart-summary';
3
+ export interface CartDrawerLabels {
4
+ title: string;
5
+ close: string;
6
+ empty: string;
7
+ }
8
+ export declare const defaultCartDrawerLabels: CartDrawerLabels;
9
+ export interface CartDrawerProps {
10
+ labels?: Partial<CartDrawerLabels>;
11
+ summary?: CartSummaryProps;
12
+ slots?: CartDrawerSlots;
13
+ }
14
+ export interface CartDrawerSlots {
15
+ empty?: ReactNode;
16
+ summaryNote?: ReactNode;
17
+ }
18
+ export declare function CartDrawer({ labels, summary, slots }: CartDrawerProps): import('react').JSX.Element | null;
@@ -0,0 +1,5 @@
1
+ import { RefObject } from 'react';
2
+ export declare function useDrawerPresence(isOpen: boolean, backdropRef: RefObject<HTMLElement | null>): {
3
+ mounted: boolean;
4
+ state: string;
5
+ };
@@ -0,0 +1,11 @@
1
+ import { CartLine } from '@develemit/commerce-contracts';
2
+ export interface CartLineItemLabels {
3
+ remove: string;
4
+ quantity: string;
5
+ }
6
+ export declare const defaultCartLineItemLabels: CartLineItemLabels;
7
+ export interface CartLineItemProps {
8
+ line: CartLine;
9
+ labels?: Partial<CartLineItemLabels>;
10
+ }
11
+ export declare function CartLineItem({ line, labels }: CartLineItemProps): import('react').JSX.Element;
@@ -0,0 +1,10 @@
1
+ export interface CartSummaryLabels {
2
+ subtotal: string;
3
+ }
4
+ export interface CartSummaryProps {
5
+ onCheckout?: () => void;
6
+ checkoutLabel?: string;
7
+ checkoutDisabled?: boolean;
8
+ labels?: Partial<CartSummaryLabels>;
9
+ }
10
+ export declare function CartSummary({ onCheckout, checkoutLabel, checkoutDisabled, labels, }: CartSummaryProps): import('react').JSX.Element;
@@ -0,0 +1,11 @@
1
+ import { Category } from '@develemit/commerce-contracts';
2
+ export interface CategoryFilterProps {
3
+ categories: Category[];
4
+ selected?: string;
5
+ onSelect?: (slug: string | undefined) => void;
6
+ allLabel?: string;
7
+ label?: string;
8
+ formatLabel?: (category: Category) => string;
9
+ sortCategories?: (a: Category, b: Category) => number;
10
+ }
11
+ export declare function CategoryFilter({ categories, selected, onSelect, allLabel, label, formatLabel, sortCategories, }: CategoryFilterProps): import('react').JSX.Element;
@@ -0,0 +1,2 @@
1
+ import { Product } from '@develemit/commerce-contracts';
2
+ export declare function makeProduct(overrides?: Partial<Product>): Product;
@@ -0,0 +1,7 @@
1
+ export interface PriceTagProps {
2
+ priceCents: number;
3
+ currency: string;
4
+ sold?: boolean;
5
+ className?: string;
6
+ }
7
+ export declare function PriceTag({ priceCents, currency, sold, className, }: PriceTagProps): import('react').JSX.Element;
@@ -0,0 +1,9 @@
1
+ import { ProductCard as ProductCardData } from '@develemit/commerce-contracts';
2
+ import { AvailabilityLabels } from '../availability-badge/availability-badge';
3
+ export interface ProductCardProps {
4
+ product: ProductCardData;
5
+ labels?: Partial<AvailabilityLabels>;
6
+ sizes?: string;
7
+ priority?: boolean;
8
+ }
9
+ export declare function ProductCard({ product, labels, sizes, priority, }: ProductCardProps): import('react').JSX.Element;
@@ -0,0 +1,6 @@
1
+ import { ProductImage } from '@develemit/commerce-contracts';
2
+ export interface ProductGalleryProps {
3
+ images: ProductImage[];
4
+ sizes?: string;
5
+ }
6
+ export declare function ProductGallery({ images, sizes }: ProductGalleryProps): import('react').JSX.Element | null;
@@ -0,0 +1,10 @@
1
+ import { ReactNode } from 'react';
2
+ import { ProductCard as ProductCardData } from '@develemit/commerce-contracts';
3
+ import { AvailabilityLabels } from '../availability-badge/availability-badge';
4
+ export interface ProductGridProps {
5
+ products: ProductCardData[];
6
+ emptyState?: ReactNode;
7
+ labels?: Partial<AvailabilityLabels>;
8
+ priorityCount?: number;
9
+ }
10
+ export declare function ProductGrid({ products, emptyState, labels, priorityCount, }: ProductGridProps): import('react').JSX.Element;
@@ -0,0 +1,3 @@
1
+ import { ReactElement } from 'react';
2
+ import { CartStorage } from '../cart/cart-storage';
3
+ export declare function renderWithCart(ui: ReactElement, storage: CartStorage): Promise<void>;
@@ -0,0 +1,2 @@
1
+ export * from '../lib/format-price';
2
+ export * from '../views/sort-for-display';
@@ -0,0 +1 @@
1
+ export { n as formatPrice, t as sortForDisplay } from "../sort-for-display-Cj6GMeCY.js";
@@ -0,0 +1,22 @@
1
+ export type { Product, ProductCard as ProductCardData, ProductCardPage, ProductImage, ProductAvailability, Category, CartLine, CheckoutRequest, CheckoutResponse, OrderSummary, } from '@develemit/commerce-contracts';
2
+ export * from './lib/format-price';
3
+ export * from './lib/theme';
4
+ export * from './lib/provider';
5
+ export * from './components/price-tag/price-tag';
6
+ export * from './components/availability-badge/availability-badge';
7
+ export * from './components/product-card/product-card';
8
+ export * from './components/product-grid/product-grid';
9
+ export * from './components/category-filter/category-filter';
10
+ export * from './components/product-gallery/product-gallery';
11
+ export * from './views/shop-view';
12
+ export * from './views/product-detail-view';
13
+ export * from './views/featured-products';
14
+ export * from './views/sort-for-display';
15
+ export * from './cart/cart-reducer';
16
+ export * from './cart/cart-storage';
17
+ export * from './cart/cart-provider';
18
+ export * from './components/add-to-cart-button/add-to-cart-button';
19
+ export * from './components/cart-drawer/cart-drawer';
20
+ export * from './components/cart-line-item/cart-line-item';
21
+ export * from './components/cart-summary/cart-summary';
22
+ export * from './components/cart-button/cart-button';