@zappie/kit 0.1.2 → 0.1.3
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.
- package/CHANGELOG.md +8 -0
- package/dist/registry/v1/items/cart-button.json +1 -1
- package/dist/registry/v1/items/cart-button@1.0.0.json +1 -1
- package/dist/registry/v1/items/cart-drawer.json +1 -1
- package/dist/registry/v1/items/cart-drawer@1.0.0.json +1 -1
- package/dist/registry/v1/items/item-card.json +1 -1
- package/dist/registry/v1/items/item-card@1.0.0.json +1 -1
- package/dist/registry/v1/items/store-header.json +1 -1
- package/dist/registry/v1/items/store-header@1.0.0.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @zappie/kit
|
|
2
2
|
|
|
3
|
+
## 0.1.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 45d1cd9: The item card holds a fixed box for its photo before the photo loads, so the menu no longer shifts when photos arrive. The grid layout shows a square photo across the card's width. The list layout shows a 124 px square, or 70 px on screens 375 px wide or narrower. Photos fill the box and are cropped to it, as on the default storefront.
|
|
8
|
+
- 3d0d559: The store header fits the business logo the way the default storefront does: a wide logo is contained so its ends show, and a square or tall one covers its box. The cover photo keeps a 12 by 5 box while it loads, so the header no longer grows when it arrives.
|
|
9
|
+
- 6595b0e: The cart drawer links to checkout. Pass `checkoutHref` and the drawer shows a Checkout link once the cart has lines. While a blocking issue is open the link is disabled and says why. The starter's cart page shows the same link, so shoppers reach checkout without typing its address.
|
|
10
|
+
|
|
3
11
|
## 0.1.2
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://registry.zappie.ai/v1/schema/item.json","name":"cart-button","type":"component","version":"1.0.0","title":"Cart button","description":"Open the cart from a floating or inline button with count and subtotal.","requires":{"@zappie/sdk":"^0.1.0","@zappie/tokens":"^0.1.0"},"dependencies":{"lucide-react":"^0.546.0"},"registryDependencies":["money"],"files":[{"path":"cart-button.tsx","target":"components/zappie/cart-button.tsx","sha256":"
|
|
1
|
+
{"$schema":"https://registry.zappie.ai/v1/schema/item.json","name":"cart-button","type":"component","version":"1.0.0","title":"Cart button","description":"Open the cart from a floating or inline button with count and subtotal.","requires":{"@zappie/sdk":"^0.1.0","@zappie/tokens":"^0.1.0"},"dependencies":{"lucide-react":"^0.546.0"},"registryDependencies":["money"],"files":[{"path":"cart-button.tsx","target":"components/zappie/cart-button.tsx","sha256":"082c691b672dc1c6e4838980a3d6d62b8cb7d8320ad061862bf02288b33dadd7","content":"\"use client\";\n\nimport { Money } from \"@/components/zappie/money\";\nimport { useCart } from \"@zappie/sdk/react\";\nimport { ShoppingBag } from \"lucide-react\";\nimport \"./cart-button.css\";\n\nexport type CartButtonProps = {\n onOpen: () => void;\n /** Called when the shopper reaches for the button, to start loading the cart's code. */\n onIntent?: () => void;\n variant?: \"floating\" | \"inline\";\n};\n\nexport function CartButton({\n onOpen,\n onIntent,\n variant = \"floating\",\n}: CartButtonProps) {\n const cart = useCart();\n return (\n <button\n type=\"button\"\n className=\"z-kit-cart-button\"\n data-variant={variant}\n data-zappie-cart-trigger=\"\"\n data-zappie-overlay-trigger=\"bag\"\n aria-label={`Open cart, ${cart.count} ${cart.count === 1 ? \"item\" : \"items\"}`}\n disabled={!cart.isHydrated}\n onClick={onOpen}\n onPointerEnter={onIntent}\n onPointerDown={onIntent}\n onFocus={onIntent}\n >\n <ShoppingBag aria-hidden=\"true\" />\n <span>Cart</span>\n <span aria-hidden=\"true\">{cart.count}</span>\n {cart.subtotal && <Money value={cart.subtotal} />}\n </button>\n );\n}\n"},{"path":"cart-button.css","target":"components/zappie/cart-button.css","sha256":"0e6879dbab4158f6fd1979696663cdc7c80621867a90acf8a4040987bf3e9216","content":".z-kit-cart-button {\n display: inline-flex;\n align-items: center;\n gap: var(--z-space-2);\n border: 0;\n border-radius: var(--z-radius-pill);\n padding: var(--z-space-3) var(--z-space-5);\n background: var(--z-accent);\n color: var(--z-accent-contrast);\n font-family: var(--z-font-body);\n font-size: var(--z-text-md);\n cursor: pointer;\n}\n\n.z-kit-cart-button[data-variant=\"floating\"] {\n position: fixed;\n right: var(--z-space-4);\n bottom: var(--z-space-4);\n border: 1px solid var(--z-border);\n}\n\n.z-kit-cart-button:focus-visible {\n outline: 2px solid var(--z-focus-ring);\n outline-offset: 2px;\n}\n\n.z-kit-cart-button:disabled {\n background: var(--z-border);\n color: var(--z-text-muted);\n cursor: not-allowed;\n}\n\n.z-kit-cart-button svg {\n width: var(--z-text-lg);\n height: var(--z-text-lg);\n}\n"}],"tokens":["--z-space-2","--z-space-3","--z-space-4","--z-space-5","--z-border","--z-radius-pill","--z-accent","--z-accent-contrast","--z-text-muted","--z-focus-ring","--z-font-body","--z-text-md","--z-text-lg"],"frameworks":["react-router","next","vite","astro-react"],"docs":"https://docs.zappie.ai/kit/cart-button"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://registry.zappie.ai/v1/schema/item.json","name":"cart-button","type":"component","version":"1.0.0","title":"Cart button","description":"Open the cart from a floating or inline button with count and subtotal.","requires":{"@zappie/sdk":"^0.1.0","@zappie/tokens":"^0.1.0"},"dependencies":{"lucide-react":"^0.546.0"},"registryDependencies":["money"],"files":[{"path":"cart-button.tsx","target":"components/zappie/cart-button.tsx","sha256":"
|
|
1
|
+
{"$schema":"https://registry.zappie.ai/v1/schema/item.json","name":"cart-button","type":"component","version":"1.0.0","title":"Cart button","description":"Open the cart from a floating or inline button with count and subtotal.","requires":{"@zappie/sdk":"^0.1.0","@zappie/tokens":"^0.1.0"},"dependencies":{"lucide-react":"^0.546.0"},"registryDependencies":["money"],"files":[{"path":"cart-button.tsx","target":"components/zappie/cart-button.tsx","sha256":"082c691b672dc1c6e4838980a3d6d62b8cb7d8320ad061862bf02288b33dadd7","content":"\"use client\";\n\nimport { Money } from \"@/components/zappie/money\";\nimport { useCart } from \"@zappie/sdk/react\";\nimport { ShoppingBag } from \"lucide-react\";\nimport \"./cart-button.css\";\n\nexport type CartButtonProps = {\n onOpen: () => void;\n /** Called when the shopper reaches for the button, to start loading the cart's code. */\n onIntent?: () => void;\n variant?: \"floating\" | \"inline\";\n};\n\nexport function CartButton({\n onOpen,\n onIntent,\n variant = \"floating\",\n}: CartButtonProps) {\n const cart = useCart();\n return (\n <button\n type=\"button\"\n className=\"z-kit-cart-button\"\n data-variant={variant}\n data-zappie-cart-trigger=\"\"\n data-zappie-overlay-trigger=\"bag\"\n aria-label={`Open cart, ${cart.count} ${cart.count === 1 ? \"item\" : \"items\"}`}\n disabled={!cart.isHydrated}\n onClick={onOpen}\n onPointerEnter={onIntent}\n onPointerDown={onIntent}\n onFocus={onIntent}\n >\n <ShoppingBag aria-hidden=\"true\" />\n <span>Cart</span>\n <span aria-hidden=\"true\">{cart.count}</span>\n {cart.subtotal && <Money value={cart.subtotal} />}\n </button>\n );\n}\n"},{"path":"cart-button.css","target":"components/zappie/cart-button.css","sha256":"0e6879dbab4158f6fd1979696663cdc7c80621867a90acf8a4040987bf3e9216","content":".z-kit-cart-button {\n display: inline-flex;\n align-items: center;\n gap: var(--z-space-2);\n border: 0;\n border-radius: var(--z-radius-pill);\n padding: var(--z-space-3) var(--z-space-5);\n background: var(--z-accent);\n color: var(--z-accent-contrast);\n font-family: var(--z-font-body);\n font-size: var(--z-text-md);\n cursor: pointer;\n}\n\n.z-kit-cart-button[data-variant=\"floating\"] {\n position: fixed;\n right: var(--z-space-4);\n bottom: var(--z-space-4);\n border: 1px solid var(--z-border);\n}\n\n.z-kit-cart-button:focus-visible {\n outline: 2px solid var(--z-focus-ring);\n outline-offset: 2px;\n}\n\n.z-kit-cart-button:disabled {\n background: var(--z-border);\n color: var(--z-text-muted);\n cursor: not-allowed;\n}\n\n.z-kit-cart-button svg {\n width: var(--z-text-lg);\n height: var(--z-text-lg);\n}\n"}],"tokens":["--z-space-2","--z-space-3","--z-space-4","--z-space-5","--z-border","--z-radius-pill","--z-accent","--z-accent-contrast","--z-text-muted","--z-focus-ring","--z-font-body","--z-text-md","--z-text-lg"],"frameworks":["react-router","next","vite","astro-react"],"docs":"https://docs.zappie.ai/kit/cart-button"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://registry.zappie.ai/v1/schema/item.json","name":"cart-drawer","type":"component","version":"1.0.0","title":"Cart drawer","description":"Show cart lines, issues and totals in a responsive drawer.","requires":{"@zappie/foundations":"^0.1.0","@zappie/sdk":"^0.1.0","@zappie/tokens":"^0.1.0"},"dependencies":{},"registryDependencies":["money","quantity","cart-line"],"files":[{"path":"cart-drawer.tsx","target":"components/zappie/cart-drawer.tsx","sha256":"
|
|
1
|
+
{"$schema":"https://registry.zappie.ai/v1/schema/item.json","name":"cart-drawer","type":"component","version":"1.0.0","title":"Cart drawer","description":"Show cart lines, issues and totals in a responsive drawer.","requires":{"@zappie/foundations":"^0.1.0","@zappie/sdk":"^0.1.0","@zappie/tokens":"^0.1.0"},"dependencies":{},"registryDependencies":["money","quantity","cart-line"],"files":[{"path":"cart-drawer.tsx","target":"components/zappie/cart-drawer.tsx","sha256":"30c5a562025ad95f1863531260e9156559d1f6df24ac60f8d5b92b9f65ce0ab1","content":"\"use client\";\n\nimport { CartLine } from \"@/components/zappie/cart-line\";\nimport { Money } from \"@/components/zappie/money\";\nimport { Cart, Overlay, Totals } from \"@zappie/foundations\";\nimport type { CartFix, CartIssue } from \"@zappie/sdk\";\nimport { countEvent } from \"@zappie/sdk/visits\";\nimport {\n Fragment,\n useEffect,\n useId,\n useMemo,\n useRef,\n type ReactNode,\n} from \"react\";\nimport \"./cart-drawer.css\";\n\nexport type CartDrawerProps = {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /**\n * Where the Checkout link goes, built with `buildStorefrontUrl()` from `@zappie/sdk/url`.\n * The drawer shows no Checkout link when this is not set.\n */\n checkoutHref?: string;\n};\n\nexport function CartDrawer({\n open,\n onOpenChange,\n checkoutHref,\n}: CartDrawerProps) {\n const countedOpen = useRef(false);\n useEffect(() => {\n if (open && !countedOpen.current) {\n countEvent(\"bag\");\n }\n countedOpen.current = open;\n }, [open]);\n const navigation = useMemo(\n () => ({\n isOpen: open,\n open: () => onOpenChange(true),\n close: () => onOpenChange(false),\n }),\n [onOpenChange, open],\n );\n return (\n <Overlay.Root\n param=\"cart\"\n id=\"drawer\"\n navigation={navigation}\n presentation=\"sheet\"\n >\n <Overlay.Content className=\"z-kit-cart-drawer\" data-zappie-overlay=\"bag\">\n <header>\n <Overlay.Title>Your cart</Overlay.Title>\n <Overlay.Close aria-label=\"Close cart\">×</Overlay.Close>\n </header>\n <Cart.Root className=\"z-kit-cart-drawer__body\">\n {(cart) => (\n <Fragment>\n <Cart.Empty>Your cart is empty.</Cart.Empty>\n {cart.lines.map((line) => (\n <CartLine key={line.id} line={line} />\n ))}\n <Cart.Issues>\n {({ issues, applyFix }) => (\n <CartIssueList issues={issues} onFix={applyFix} />\n )}\n </Cart.Issues>\n {cart.lines.length > 0 && (\n <CartTotals>\n {checkoutHref && (\n <CartCheckoutLink\n href={checkoutHref}\n blocked={cart.issues.some(\n (issue) => issue.severity === \"blocking\",\n )}\n />\n )}\n </CartTotals>\n )}\n </Fragment>\n )}\n </Cart.Root>\n </Overlay.Content>\n </Overlay.Root>\n );\n}\n\n// Only fixes the SDK applies itself; the others need an editor the drawer doesn't have.\nconst fixLabels: Partial<Record<CartFix[\"action\"], string>> = {\n remove_line: \"Remove item\",\n accept_price: \"Accept new price\",\n remove_promo: \"Remove promo\",\n set_quantity: \"Update quantity\",\n};\n\nexport function CartIssueList({\n issues,\n onFix,\n}: {\n issues: readonly CartIssue[];\n onFix: (issue: CartIssue, fix: CartFix) => void;\n}) {\n if (issues.length === 0) {\n return null;\n }\n return (\n <div className=\"z-kit-cart-drawer__issues\" role=\"alert\">\n {issues.map((issue, index) => (\n <div\n key={`${issue.code}:${issue.lineId ?? \"cart\"}:${index}`}\n data-zappie-issue={issue.code}\n >\n <p>{issue.message}</p>\n {issue.fixes\n .filter((fix) => fixLabels[fix.action])\n .map((fix, fixIndex) => (\n <button\n key={`${fix.action}:${fixIndex}`}\n type=\"button\"\n data-zappie-issue-fix=\"\"\n onClick={() => onFix(issue, fix)}\n >\n {fix.action === \"set_quantity\"\n ? `Set quantity to ${fix.quantity}`\n : fixLabels[fix.action]}\n </button>\n ))}\n </div>\n ))}\n </div>\n );\n}\n\n/**\n * The link from the cart to checkout. While a blocking issue is open it is a disabled button that says why.\n */\nexport function CartCheckoutLink({\n href,\n blocked,\n}: {\n href: string;\n blocked: boolean;\n}) {\n const reason = useId();\n if (blocked) {\n return (\n <Fragment>\n <button\n type=\"button\"\n className=\"z-kit-cart-checkout\"\n disabled\n aria-describedby={reason}\n data-zappie-cart-checkout=\"\"\n >\n Checkout\n </button>\n <p id={reason} className=\"z-kit-cart-checkout__reason\">\n Fix the issues in your cart to check out.\n </p>\n </Fragment>\n );\n }\n return (\n <a className=\"z-kit-cart-checkout\" href={href} data-zappie-cart-checkout=\"\">\n Checkout\n </a>\n );\n}\n\nfunction CartTotals({ children }: { children?: ReactNode }) {\n return (\n <Totals className=\"z-kit-cart-drawer__totals\">\n {({ quote, status }) => (\n <div>\n {status === \"loading\" && <p role=\"status\">Updating total…</p>}\n {(status === \"stale\" || status === \"offline\") && (\n <p role=\"alert\">Total unavailable. Try again.</p>\n )}\n {quote && (\n <p data-zappie-total=\"\">\n <strong>Total</strong> <Money value={quote.total} />\n </p>\n )}\n {children}\n </div>\n )}\n </Totals>\n );\n}\n"},{"path":"cart-drawer.css","target":"components/zappie/cart-drawer.css","sha256":"cd93c005860b4cc6008c771fd5bd859b5b8fb5ff26801d41a30c1bac609e02ce","content":".z-kit-cart-drawer {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr);\n width: 100%;\n max-height: 90vh;\n background: var(--z-surface);\n color: var(--z-text);\n font-family: var(--z-font-body);\n}\n\n.z-kit-cart-drawer > header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--z-space-3);\n padding: var(--z-space-4);\n border-bottom: 1px solid var(--z-border);\n}\n\n.z-kit-cart-drawer__body {\n overflow-y: auto;\n padding: 0 var(--z-space-4) var(--z-space-4);\n}\n\n.z-kit-cart-drawer__issues {\n margin-top: var(--z-space-3);\n border-radius: var(--z-radius-md);\n padding: var(--z-space-3);\n border-inline-start: 3px solid var(--z-danger);\n background: var(--z-surface-raised);\n color: var(--z-text);\n}\n\n.z-kit-cart-drawer__totals {\n position: sticky;\n bottom: 0;\n display: block;\n padding: var(--z-space-4) 0;\n background: var(--z-surface);\n}\n\n.z-kit-cart-drawer button:focus-visible {\n outline: 2px solid var(--z-focus-ring);\n outline-offset: 2px;\n}\n\n@media (min-width: 64rem) {\n .z-kit-cart-drawer {\n width: min(var(--z-drawer-width), 100vw);\n height: 100vh;\n max-height: none;\n margin-left: auto;\n }\n}\n\n.z-kit-cart-checkout {\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: 100%;\n min-height: 44px;\n margin-top: var(--z-space-3);\n border-radius: var(--z-radius-md);\n padding: var(--z-space-3) var(--z-space-5);\n background: var(--z-accent);\n color: var(--z-accent-contrast);\n font-family: var(--z-font-body);\n font-size: var(--z-text-md);\n border: 0;\n text-decoration: none;\n cursor: pointer;\n}\n\n.z-kit-cart-checkout:disabled {\n background: var(--z-border);\n color: var(--z-text-muted);\n cursor: not-allowed;\n}\n\n.z-kit-cart-checkout:focus-visible {\n outline: 2px solid var(--z-focus-ring);\n outline-offset: 2px;\n}\n\n.z-kit-cart-checkout__reason {\n margin: var(--z-space-2) 0 0;\n color: var(--z-text-muted);\n}\n"}],"tokens":["--z-space-3","--z-space-4","--z-border","--z-radius-md","--z-surface","--z-surface-raised","--z-text","--z-danger","--z-focus-ring","--z-font-body","--z-drawer-width","--z-space-2","--z-space-5","--z-text-md","--z-text-muted","--z-accent","--z-accent-contrast"],"frameworks":["react-router","next","vite","astro-react"],"docs":"https://docs.zappie.ai/kit/cart-drawer"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://registry.zappie.ai/v1/schema/item.json","name":"cart-drawer","type":"component","version":"1.0.0","title":"Cart drawer","description":"Show cart lines, issues and totals in a responsive drawer.","requires":{"@zappie/foundations":"^0.1.0","@zappie/sdk":"^0.1.0","@zappie/tokens":"^0.1.0"},"dependencies":{},"registryDependencies":["money","quantity","cart-line"],"files":[{"path":"cart-drawer.tsx","target":"components/zappie/cart-drawer.tsx","sha256":"
|
|
1
|
+
{"$schema":"https://registry.zappie.ai/v1/schema/item.json","name":"cart-drawer","type":"component","version":"1.0.0","title":"Cart drawer","description":"Show cart lines, issues and totals in a responsive drawer.","requires":{"@zappie/foundations":"^0.1.0","@zappie/sdk":"^0.1.0","@zappie/tokens":"^0.1.0"},"dependencies":{},"registryDependencies":["money","quantity","cart-line"],"files":[{"path":"cart-drawer.tsx","target":"components/zappie/cart-drawer.tsx","sha256":"30c5a562025ad95f1863531260e9156559d1f6df24ac60f8d5b92b9f65ce0ab1","content":"\"use client\";\n\nimport { CartLine } from \"@/components/zappie/cart-line\";\nimport { Money } from \"@/components/zappie/money\";\nimport { Cart, Overlay, Totals } from \"@zappie/foundations\";\nimport type { CartFix, CartIssue } from \"@zappie/sdk\";\nimport { countEvent } from \"@zappie/sdk/visits\";\nimport {\n Fragment,\n useEffect,\n useId,\n useMemo,\n useRef,\n type ReactNode,\n} from \"react\";\nimport \"./cart-drawer.css\";\n\nexport type CartDrawerProps = {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /**\n * Where the Checkout link goes, built with `buildStorefrontUrl()` from `@zappie/sdk/url`.\n * The drawer shows no Checkout link when this is not set.\n */\n checkoutHref?: string;\n};\n\nexport function CartDrawer({\n open,\n onOpenChange,\n checkoutHref,\n}: CartDrawerProps) {\n const countedOpen = useRef(false);\n useEffect(() => {\n if (open && !countedOpen.current) {\n countEvent(\"bag\");\n }\n countedOpen.current = open;\n }, [open]);\n const navigation = useMemo(\n () => ({\n isOpen: open,\n open: () => onOpenChange(true),\n close: () => onOpenChange(false),\n }),\n [onOpenChange, open],\n );\n return (\n <Overlay.Root\n param=\"cart\"\n id=\"drawer\"\n navigation={navigation}\n presentation=\"sheet\"\n >\n <Overlay.Content className=\"z-kit-cart-drawer\" data-zappie-overlay=\"bag\">\n <header>\n <Overlay.Title>Your cart</Overlay.Title>\n <Overlay.Close aria-label=\"Close cart\">×</Overlay.Close>\n </header>\n <Cart.Root className=\"z-kit-cart-drawer__body\">\n {(cart) => (\n <Fragment>\n <Cart.Empty>Your cart is empty.</Cart.Empty>\n {cart.lines.map((line) => (\n <CartLine key={line.id} line={line} />\n ))}\n <Cart.Issues>\n {({ issues, applyFix }) => (\n <CartIssueList issues={issues} onFix={applyFix} />\n )}\n </Cart.Issues>\n {cart.lines.length > 0 && (\n <CartTotals>\n {checkoutHref && (\n <CartCheckoutLink\n href={checkoutHref}\n blocked={cart.issues.some(\n (issue) => issue.severity === \"blocking\",\n )}\n />\n )}\n </CartTotals>\n )}\n </Fragment>\n )}\n </Cart.Root>\n </Overlay.Content>\n </Overlay.Root>\n );\n}\n\n// Only fixes the SDK applies itself; the others need an editor the drawer doesn't have.\nconst fixLabels: Partial<Record<CartFix[\"action\"], string>> = {\n remove_line: \"Remove item\",\n accept_price: \"Accept new price\",\n remove_promo: \"Remove promo\",\n set_quantity: \"Update quantity\",\n};\n\nexport function CartIssueList({\n issues,\n onFix,\n}: {\n issues: readonly CartIssue[];\n onFix: (issue: CartIssue, fix: CartFix) => void;\n}) {\n if (issues.length === 0) {\n return null;\n }\n return (\n <div className=\"z-kit-cart-drawer__issues\" role=\"alert\">\n {issues.map((issue, index) => (\n <div\n key={`${issue.code}:${issue.lineId ?? \"cart\"}:${index}`}\n data-zappie-issue={issue.code}\n >\n <p>{issue.message}</p>\n {issue.fixes\n .filter((fix) => fixLabels[fix.action])\n .map((fix, fixIndex) => (\n <button\n key={`${fix.action}:${fixIndex}`}\n type=\"button\"\n data-zappie-issue-fix=\"\"\n onClick={() => onFix(issue, fix)}\n >\n {fix.action === \"set_quantity\"\n ? `Set quantity to ${fix.quantity}`\n : fixLabels[fix.action]}\n </button>\n ))}\n </div>\n ))}\n </div>\n );\n}\n\n/**\n * The link from the cart to checkout. While a blocking issue is open it is a disabled button that says why.\n */\nexport function CartCheckoutLink({\n href,\n blocked,\n}: {\n href: string;\n blocked: boolean;\n}) {\n const reason = useId();\n if (blocked) {\n return (\n <Fragment>\n <button\n type=\"button\"\n className=\"z-kit-cart-checkout\"\n disabled\n aria-describedby={reason}\n data-zappie-cart-checkout=\"\"\n >\n Checkout\n </button>\n <p id={reason} className=\"z-kit-cart-checkout__reason\">\n Fix the issues in your cart to check out.\n </p>\n </Fragment>\n );\n }\n return (\n <a className=\"z-kit-cart-checkout\" href={href} data-zappie-cart-checkout=\"\">\n Checkout\n </a>\n );\n}\n\nfunction CartTotals({ children }: { children?: ReactNode }) {\n return (\n <Totals className=\"z-kit-cart-drawer__totals\">\n {({ quote, status }) => (\n <div>\n {status === \"loading\" && <p role=\"status\">Updating total…</p>}\n {(status === \"stale\" || status === \"offline\") && (\n <p role=\"alert\">Total unavailable. Try again.</p>\n )}\n {quote && (\n <p data-zappie-total=\"\">\n <strong>Total</strong> <Money value={quote.total} />\n </p>\n )}\n {children}\n </div>\n )}\n </Totals>\n );\n}\n"},{"path":"cart-drawer.css","target":"components/zappie/cart-drawer.css","sha256":"cd93c005860b4cc6008c771fd5bd859b5b8fb5ff26801d41a30c1bac609e02ce","content":".z-kit-cart-drawer {\n display: grid;\n grid-template-rows: auto minmax(0, 1fr);\n width: 100%;\n max-height: 90vh;\n background: var(--z-surface);\n color: var(--z-text);\n font-family: var(--z-font-body);\n}\n\n.z-kit-cart-drawer > header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--z-space-3);\n padding: var(--z-space-4);\n border-bottom: 1px solid var(--z-border);\n}\n\n.z-kit-cart-drawer__body {\n overflow-y: auto;\n padding: 0 var(--z-space-4) var(--z-space-4);\n}\n\n.z-kit-cart-drawer__issues {\n margin-top: var(--z-space-3);\n border-radius: var(--z-radius-md);\n padding: var(--z-space-3);\n border-inline-start: 3px solid var(--z-danger);\n background: var(--z-surface-raised);\n color: var(--z-text);\n}\n\n.z-kit-cart-drawer__totals {\n position: sticky;\n bottom: 0;\n display: block;\n padding: var(--z-space-4) 0;\n background: var(--z-surface);\n}\n\n.z-kit-cart-drawer button:focus-visible {\n outline: 2px solid var(--z-focus-ring);\n outline-offset: 2px;\n}\n\n@media (min-width: 64rem) {\n .z-kit-cart-drawer {\n width: min(var(--z-drawer-width), 100vw);\n height: 100vh;\n max-height: none;\n margin-left: auto;\n }\n}\n\n.z-kit-cart-checkout {\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n width: 100%;\n min-height: 44px;\n margin-top: var(--z-space-3);\n border-radius: var(--z-radius-md);\n padding: var(--z-space-3) var(--z-space-5);\n background: var(--z-accent);\n color: var(--z-accent-contrast);\n font-family: var(--z-font-body);\n font-size: var(--z-text-md);\n border: 0;\n text-decoration: none;\n cursor: pointer;\n}\n\n.z-kit-cart-checkout:disabled {\n background: var(--z-border);\n color: var(--z-text-muted);\n cursor: not-allowed;\n}\n\n.z-kit-cart-checkout:focus-visible {\n outline: 2px solid var(--z-focus-ring);\n outline-offset: 2px;\n}\n\n.z-kit-cart-checkout__reason {\n margin: var(--z-space-2) 0 0;\n color: var(--z-text-muted);\n}\n"}],"tokens":["--z-space-3","--z-space-4","--z-border","--z-radius-md","--z-surface","--z-surface-raised","--z-text","--z-danger","--z-focus-ring","--z-font-body","--z-drawer-width","--z-space-2","--z-space-5","--z-text-md","--z-text-muted","--z-accent","--z-accent-contrast"],"frameworks":["react-router","next","vite","astro-react"],"docs":"https://docs.zappie.ai/kit/cart-drawer"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://registry.zappie.ai/v1/schema/item.json","name":"item-card","type":"component","version":"1.0.0","title":"Item card","description":"Show an item, its price, availability and cart feedback.","requires":{"@zappie/sdk":"^0.1.0","@zappie/foundations":"^0.1.0","@zappie/tokens":"^0.1.0"},"dependencies":{"lucide-react":"^0.546.0"},"registryDependencies":["money","quantity"],"files":[{"path":"item-card.tsx","target":"components/zappie/item-card.tsx","sha256":"859e3770d72434d89acb4fa9a17e9cd56ca5a37913fe14d31dd1c2717a711860","content":"\"use client\";\n\nimport { Money } from \"@/components/zappie/money\";\nimport { useCart, useItem } from \"@zappie/sdk/react\";\nimport { countEvent } from \"@zappie/sdk/visits\";\nimport { Check } from \"lucide-react\";\nimport \"./item-card.css\";\n\nexport type ItemCardProps = {\n itemId: string;\n branch?: string;\n variant?: \"grid\" | \"list\";\n};\n\nexport function ItemCard({ itemId, branch, variant = \"grid\" }: ItemCardProps) {\n const item = useItem(itemId, branch);\n const cart = useCart();\n if (item.isLoading) {\n return <article className=\"z-kit-item-card\">Loading item…</article>;\n }\n if (item.error || !item.data) {\n return (\n <article className=\"z-kit-item-card\" role=\"alert\">\n {item.error?.message ?? \"Item unavailable\"}\n </article>\n );\n }\n const value = item.data;\n const added = cart.lines.some((line) => line.itemId === itemId);\n const soldOut = !value.isAvailable;\n return (\n <article\n className=\"z-kit-item-card\"\n data-variant={variant}\n data-zappie-item={value.id}\n >\n {value.images[0] && (\n <img\n className=\"z-kit-item-card__image\"\n src={value.images[0]}\n alt={value.name}\n />\n )}\n <div className=\"z-kit-item-card__content\">\n <div>\n <h3>{value.name}</h3>\n <Money value={value.price} />\n </div>\n {soldOut && <span className=\"z-kit-item-card__sold-out\">Sold out</span>}\n <button\n type=\"button\"\n disabled={soldOut || !cart.isHydrated}\n data-zappie-action=\"add-to-cart\"\n onClick={() => {\n cart.actions.add(value);\n countEvent(\"add\");\n }}\n >\n {added && <Check aria-hidden=\"true\" />}\n {added ? \"Added\" : \"Add to cart\"}\n </button>\n </div>\n </article>\n );\n}\n"},{"path":"item-card.css","target":"components/zappie/item-card.css","sha256":"
|
|
1
|
+
{"$schema":"https://registry.zappie.ai/v1/schema/item.json","name":"item-card","type":"component","version":"1.0.0","title":"Item card","description":"Show an item, its price, availability and cart feedback.","requires":{"@zappie/sdk":"^0.1.0","@zappie/foundations":"^0.1.0","@zappie/tokens":"^0.1.0"},"dependencies":{"lucide-react":"^0.546.0"},"registryDependencies":["money","quantity"],"files":[{"path":"item-card.tsx","target":"components/zappie/item-card.tsx","sha256":"859e3770d72434d89acb4fa9a17e9cd56ca5a37913fe14d31dd1c2717a711860","content":"\"use client\";\n\nimport { Money } from \"@/components/zappie/money\";\nimport { useCart, useItem } from \"@zappie/sdk/react\";\nimport { countEvent } from \"@zappie/sdk/visits\";\nimport { Check } from \"lucide-react\";\nimport \"./item-card.css\";\n\nexport type ItemCardProps = {\n itemId: string;\n branch?: string;\n variant?: \"grid\" | \"list\";\n};\n\nexport function ItemCard({ itemId, branch, variant = \"grid\" }: ItemCardProps) {\n const item = useItem(itemId, branch);\n const cart = useCart();\n if (item.isLoading) {\n return <article className=\"z-kit-item-card\">Loading item…</article>;\n }\n if (item.error || !item.data) {\n return (\n <article className=\"z-kit-item-card\" role=\"alert\">\n {item.error?.message ?? \"Item unavailable\"}\n </article>\n );\n }\n const value = item.data;\n const added = cart.lines.some((line) => line.itemId === itemId);\n const soldOut = !value.isAvailable;\n return (\n <article\n className=\"z-kit-item-card\"\n data-variant={variant}\n data-zappie-item={value.id}\n >\n {value.images[0] && (\n <img\n className=\"z-kit-item-card__image\"\n src={value.images[0]}\n alt={value.name}\n />\n )}\n <div className=\"z-kit-item-card__content\">\n <div>\n <h3>{value.name}</h3>\n <Money value={value.price} />\n </div>\n {soldOut && <span className=\"z-kit-item-card__sold-out\">Sold out</span>}\n <button\n type=\"button\"\n disabled={soldOut || !cart.isHydrated}\n data-zappie-action=\"add-to-cart\"\n onClick={() => {\n cart.actions.add(value);\n countEvent(\"add\");\n }}\n >\n {added && <Check aria-hidden=\"true\" />}\n {added ? \"Added\" : \"Add to cart\"}\n </button>\n </div>\n </article>\n );\n}\n"},{"path":"item-card.css","target":"components/zappie/item-card.css","sha256":"60424b10a12a225213b7f2ada864d38fb027e75156842a7a6d2ee2d23e9039be","content":".z-kit-item-card {\n display: grid;\n gap: var(--z-space-3);\n overflow: hidden;\n border: 1px solid var(--z-border);\n border-radius: var(--z-radius-lg);\n background: var(--z-surface);\n color: var(--z-text);\n font-family: var(--z-font-body);\n}\n\n.z-kit-item-card[data-variant=\"list\"] {\n grid-template-columns: 124px 1fr;\n align-items: start;\n}\n\n/* Each layout holds a fixed box for the photo before it loads, so the menu doesn't shift when it arrives. */\n.z-kit-item-card__image {\n display: block;\n width: 100%;\n max-height: 180px;\n aspect-ratio: 1 / 1;\n object-fit: cover;\n}\n\n@media (min-width: 40rem) {\n .z-kit-item-card__image {\n max-height: 200px;\n }\n}\n\n.z-kit-item-card[data-variant=\"list\"] .z-kit-item-card__image {\n width: 124px;\n height: 124px;\n max-height: none;\n}\n\n@media (max-width: 375px) {\n .z-kit-item-card[data-variant=\"list\"] {\n grid-template-columns: 70px 1fr;\n }\n\n .z-kit-item-card[data-variant=\"list\"] .z-kit-item-card__image {\n width: 70px;\n height: 70px;\n }\n}\n\n.z-kit-item-card__content {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: var(--z-space-3);\n padding: var(--z-space-4);\n}\n\n.z-kit-item-card h3 {\n margin: 0 0 var(--z-space-1);\n font-family: var(--z-font-display);\n font-size: var(--z-text-lg);\n}\n\n.z-kit-item-card__sold-out {\n color: var(--z-text);\n font-size: var(--z-text-sm);\n}\n\n.z-kit-item-card button {\n display: inline-flex;\n align-items: center;\n gap: var(--z-space-2);\n border: 0;\n border-radius: var(--z-radius-pill);\n padding: var(--z-space-2) var(--z-space-4);\n background: var(--z-accent);\n color: var(--z-accent-contrast);\n font: inherit;\n cursor: pointer;\n}\n\n.z-kit-item-card button:focus-visible {\n outline: 2px solid var(--z-focus-ring);\n outline-offset: 2px;\n}\n\n.z-kit-item-card button:disabled {\n background: var(--z-border);\n color: var(--z-text-muted);\n cursor: not-allowed;\n}\n\n.z-kit-item-card button svg {\n width: var(--z-text-md);\n height: var(--z-text-md);\n}\n"}],"tokens":["--z-space-1","--z-space-2","--z-space-3","--z-space-4","--z-border","--z-radius-lg","--z-radius-pill","--z-surface","--z-text","--z-text-muted","--z-danger","--z-accent","--z-accent-contrast","--z-focus-ring","--z-font-body","--z-font-display","--z-text-sm","--z-text-md","--z-text-lg","--z-media-sm"],"frameworks":["react-router","next","vite","astro-react"],"docs":"https://docs.zappie.ai/kit/item-card"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://registry.zappie.ai/v1/schema/item.json","name":"item-card","type":"component","version":"1.0.0","title":"Item card","description":"Show an item, its price, availability and cart feedback.","requires":{"@zappie/sdk":"^0.1.0","@zappie/foundations":"^0.1.0","@zappie/tokens":"^0.1.0"},"dependencies":{"lucide-react":"^0.546.0"},"registryDependencies":["money","quantity"],"files":[{"path":"item-card.tsx","target":"components/zappie/item-card.tsx","sha256":"859e3770d72434d89acb4fa9a17e9cd56ca5a37913fe14d31dd1c2717a711860","content":"\"use client\";\n\nimport { Money } from \"@/components/zappie/money\";\nimport { useCart, useItem } from \"@zappie/sdk/react\";\nimport { countEvent } from \"@zappie/sdk/visits\";\nimport { Check } from \"lucide-react\";\nimport \"./item-card.css\";\n\nexport type ItemCardProps = {\n itemId: string;\n branch?: string;\n variant?: \"grid\" | \"list\";\n};\n\nexport function ItemCard({ itemId, branch, variant = \"grid\" }: ItemCardProps) {\n const item = useItem(itemId, branch);\n const cart = useCart();\n if (item.isLoading) {\n return <article className=\"z-kit-item-card\">Loading item…</article>;\n }\n if (item.error || !item.data) {\n return (\n <article className=\"z-kit-item-card\" role=\"alert\">\n {item.error?.message ?? \"Item unavailable\"}\n </article>\n );\n }\n const value = item.data;\n const added = cart.lines.some((line) => line.itemId === itemId);\n const soldOut = !value.isAvailable;\n return (\n <article\n className=\"z-kit-item-card\"\n data-variant={variant}\n data-zappie-item={value.id}\n >\n {value.images[0] && (\n <img\n className=\"z-kit-item-card__image\"\n src={value.images[0]}\n alt={value.name}\n />\n )}\n <div className=\"z-kit-item-card__content\">\n <div>\n <h3>{value.name}</h3>\n <Money value={value.price} />\n </div>\n {soldOut && <span className=\"z-kit-item-card__sold-out\">Sold out</span>}\n <button\n type=\"button\"\n disabled={soldOut || !cart.isHydrated}\n data-zappie-action=\"add-to-cart\"\n onClick={() => {\n cart.actions.add(value);\n countEvent(\"add\");\n }}\n >\n {added && <Check aria-hidden=\"true\" />}\n {added ? \"Added\" : \"Add to cart\"}\n </button>\n </div>\n </article>\n );\n}\n"},{"path":"item-card.css","target":"components/zappie/item-card.css","sha256":"
|
|
1
|
+
{"$schema":"https://registry.zappie.ai/v1/schema/item.json","name":"item-card","type":"component","version":"1.0.0","title":"Item card","description":"Show an item, its price, availability and cart feedback.","requires":{"@zappie/sdk":"^0.1.0","@zappie/foundations":"^0.1.0","@zappie/tokens":"^0.1.0"},"dependencies":{"lucide-react":"^0.546.0"},"registryDependencies":["money","quantity"],"files":[{"path":"item-card.tsx","target":"components/zappie/item-card.tsx","sha256":"859e3770d72434d89acb4fa9a17e9cd56ca5a37913fe14d31dd1c2717a711860","content":"\"use client\";\n\nimport { Money } from \"@/components/zappie/money\";\nimport { useCart, useItem } from \"@zappie/sdk/react\";\nimport { countEvent } from \"@zappie/sdk/visits\";\nimport { Check } from \"lucide-react\";\nimport \"./item-card.css\";\n\nexport type ItemCardProps = {\n itemId: string;\n branch?: string;\n variant?: \"grid\" | \"list\";\n};\n\nexport function ItemCard({ itemId, branch, variant = \"grid\" }: ItemCardProps) {\n const item = useItem(itemId, branch);\n const cart = useCart();\n if (item.isLoading) {\n return <article className=\"z-kit-item-card\">Loading item…</article>;\n }\n if (item.error || !item.data) {\n return (\n <article className=\"z-kit-item-card\" role=\"alert\">\n {item.error?.message ?? \"Item unavailable\"}\n </article>\n );\n }\n const value = item.data;\n const added = cart.lines.some((line) => line.itemId === itemId);\n const soldOut = !value.isAvailable;\n return (\n <article\n className=\"z-kit-item-card\"\n data-variant={variant}\n data-zappie-item={value.id}\n >\n {value.images[0] && (\n <img\n className=\"z-kit-item-card__image\"\n src={value.images[0]}\n alt={value.name}\n />\n )}\n <div className=\"z-kit-item-card__content\">\n <div>\n <h3>{value.name}</h3>\n <Money value={value.price} />\n </div>\n {soldOut && <span className=\"z-kit-item-card__sold-out\">Sold out</span>}\n <button\n type=\"button\"\n disabled={soldOut || !cart.isHydrated}\n data-zappie-action=\"add-to-cart\"\n onClick={() => {\n cart.actions.add(value);\n countEvent(\"add\");\n }}\n >\n {added && <Check aria-hidden=\"true\" />}\n {added ? \"Added\" : \"Add to cart\"}\n </button>\n </div>\n </article>\n );\n}\n"},{"path":"item-card.css","target":"components/zappie/item-card.css","sha256":"60424b10a12a225213b7f2ada864d38fb027e75156842a7a6d2ee2d23e9039be","content":".z-kit-item-card {\n display: grid;\n gap: var(--z-space-3);\n overflow: hidden;\n border: 1px solid var(--z-border);\n border-radius: var(--z-radius-lg);\n background: var(--z-surface);\n color: var(--z-text);\n font-family: var(--z-font-body);\n}\n\n.z-kit-item-card[data-variant=\"list\"] {\n grid-template-columns: 124px 1fr;\n align-items: start;\n}\n\n/* Each layout holds a fixed box for the photo before it loads, so the menu doesn't shift when it arrives. */\n.z-kit-item-card__image {\n display: block;\n width: 100%;\n max-height: 180px;\n aspect-ratio: 1 / 1;\n object-fit: cover;\n}\n\n@media (min-width: 40rem) {\n .z-kit-item-card__image {\n max-height: 200px;\n }\n}\n\n.z-kit-item-card[data-variant=\"list\"] .z-kit-item-card__image {\n width: 124px;\n height: 124px;\n max-height: none;\n}\n\n@media (max-width: 375px) {\n .z-kit-item-card[data-variant=\"list\"] {\n grid-template-columns: 70px 1fr;\n }\n\n .z-kit-item-card[data-variant=\"list\"] .z-kit-item-card__image {\n width: 70px;\n height: 70px;\n }\n}\n\n.z-kit-item-card__content {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: var(--z-space-3);\n padding: var(--z-space-4);\n}\n\n.z-kit-item-card h3 {\n margin: 0 0 var(--z-space-1);\n font-family: var(--z-font-display);\n font-size: var(--z-text-lg);\n}\n\n.z-kit-item-card__sold-out {\n color: var(--z-text);\n font-size: var(--z-text-sm);\n}\n\n.z-kit-item-card button {\n display: inline-flex;\n align-items: center;\n gap: var(--z-space-2);\n border: 0;\n border-radius: var(--z-radius-pill);\n padding: var(--z-space-2) var(--z-space-4);\n background: var(--z-accent);\n color: var(--z-accent-contrast);\n font: inherit;\n cursor: pointer;\n}\n\n.z-kit-item-card button:focus-visible {\n outline: 2px solid var(--z-focus-ring);\n outline-offset: 2px;\n}\n\n.z-kit-item-card button:disabled {\n background: var(--z-border);\n color: var(--z-text-muted);\n cursor: not-allowed;\n}\n\n.z-kit-item-card button svg {\n width: var(--z-text-md);\n height: var(--z-text-md);\n}\n"}],"tokens":["--z-space-1","--z-space-2","--z-space-3","--z-space-4","--z-border","--z-radius-lg","--z-radius-pill","--z-surface","--z-text","--z-text-muted","--z-danger","--z-accent","--z-accent-contrast","--z-focus-ring","--z-font-body","--z-font-display","--z-text-sm","--z-text-md","--z-text-lg","--z-media-sm"],"frameworks":["react-router","next","vite","astro-react"],"docs":"https://docs.zappie.ai/kit/item-card"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://registry.zappie.ai/v1/schema/item.json","name":"store-header","type":"component","version":"1.0.0","title":"Store header","description":"Show store details, opening status and fulfilment.","requires":{"@zappie/sdk":"^0.1.0","@zappie/tokens":"^0.1.0"},"dependencies":{},"registryDependencies":["open-status"],"files":[{"path":"store-header.tsx","target":"components/zappie/store-header.tsx","sha256":"
|
|
1
|
+
{"$schema":"https://registry.zappie.ai/v1/schema/item.json","name":"store-header","type":"component","version":"1.0.0","title":"Store header","description":"Show store details, opening status and fulfilment.","requires":{"@zappie/sdk":"^0.1.0","@zappie/tokens":"^0.1.0"},"dependencies":{},"registryDependencies":["open-status"],"files":[{"path":"store-header.tsx","target":"components/zappie/store-header.tsx","sha256":"6fa907b259d33232b80ff34efea83b7a79e783dd3e6741faf8952f26461cbfe3","content":"\"use client\";\n\nimport { OpenStatus } from \"@/components/zappie/open-status\";\nimport { useCart, useStore } from \"@zappie/sdk/react\";\nimport { useCallback, useState } from \"react\";\nimport \"./store-header.css\";\n\nexport type StoreHeaderProps = { branch?: string };\n\nexport function StoreHeader({ branch }: StoreHeaderProps) {\n const store = useStore();\n const cart = useCart();\n if (store.isLoading) {\n return <header className=\"z-kit-store-header\">Loading store…</header>;\n }\n if (store.error || !store.data) {\n return (\n <header className=\"z-kit-store-header\" role=\"alert\">\n Store unavailable\n </header>\n );\n }\n const selected =\n store.data.branches.find(\n (item) => item.id === (branch ?? cart.branchId) || item.slug === branch,\n ) ??\n store.data.branches.find((item) => item.isDefault) ??\n store.data.branches[0];\n return (\n <header className=\"z-kit-store-header\">\n {store.data.brand.coverImageUrl && (\n <img\n className=\"z-kit-store-header__cover\"\n src={store.data.brand.coverImageUrl}\n alt=\"\"\n />\n )}\n <div className=\"z-kit-store-header__identity\">\n {store.data.brand.logoUrl && <Logo src={store.data.brand.logoUrl} />}\n <div>\n <h1>{store.data.name}</h1>\n {store.data.description && <p>{store.data.description}</p>}\n </div>\n </div>\n {selected && (\n <div className=\"z-kit-store-header__summary\">\n <OpenStatus\n branch={selected}\n timezone={selected.timezone ?? store.data.timezone}\n />\n <span>{fulfilment(selected.fulfilment)}</span>\n </div>\n )}\n </header>\n );\n}\n\n// A wide logo is contained so its ends show; a square or tall one covers its box.\nfunction Logo({ src }: { src: string }) {\n const [fit, setFit] = useState<\"contain\" | \"cover\">(\"contain\");\n // A logo loaded before hydration fires no onLoad for React, so the ref reads it too.\n const measure = useCallback((image: HTMLImageElement | null) => {\n if (image?.complete && image.naturalWidth > 0) {\n setFit(image.naturalWidth > image.naturalHeight ? \"contain\" : \"cover\");\n }\n }, []);\n return (\n <img\n ref={measure}\n src={src}\n alt=\"\"\n data-fit={fit}\n onLoad={(event) => measure(event.currentTarget)}\n />\n );\n}\n\nfunction fulfilment(value: {\n pickup: boolean;\n delivery: boolean;\n dineIn: boolean;\n}) {\n return [\n value.delivery ? \"Delivery\" : \"\",\n value.pickup ? \"Pickup\" : \"\",\n value.dineIn ? \"Dine in\" : \"\",\n ]\n .filter(Boolean)\n .join(\" · \");\n}\n"},{"path":"store-header.css","target":"components/zappie/store-header.css","sha256":"b7fad541ad0e9085e97918b1efcdfabd4241a3a5af248fcae61c2abe180f4507","content":".z-kit-store-header {\n display: grid;\n gap: var(--z-space-4);\n color: var(--z-text);\n font-family: var(--z-font-body);\n}\n.z-kit-store-header__cover {\n width: 100%;\n aspect-ratio: 12 / 5;\n max-height: 20rem;\n object-fit: cover;\n border-radius: var(--z-radius-lg);\n}\n.z-kit-store-header__identity,\n.z-kit-store-header__summary {\n display: flex;\n align-items: center;\n gap: var(--z-space-3);\n}\n.z-kit-store-header__identity img {\n width: var(--z-media-sm);\n height: var(--z-media-sm);\n object-fit: cover;\n border-radius: var(--z-radius-md);\n}\n.z-kit-store-header__identity img[data-fit=\"contain\"] {\n object-fit: contain;\n}\n.z-kit-store-header h1 {\n margin: 0;\n font-family: var(--z-font-display);\n}\n.z-kit-store-header p {\n color: var(--z-text-muted);\n}\n.z-kit-store-header__summary {\n justify-content: space-between;\n}\n"}],"tokens":["--z-space-3","--z-space-4","--z-text","--z-text-muted","--z-radius-md","--z-radius-lg","--z-font-body","--z-font-display","--z-media-sm"],"frameworks":["react-router","next","vite","astro-react"],"docs":"https://docs.zappie.ai/kit/store-header"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"$schema":"https://registry.zappie.ai/v1/schema/item.json","name":"store-header","type":"component","version":"1.0.0","title":"Store header","description":"Show store details, opening status and fulfilment.","requires":{"@zappie/sdk":"^0.1.0","@zappie/tokens":"^0.1.0"},"dependencies":{},"registryDependencies":["open-status"],"files":[{"path":"store-header.tsx","target":"components/zappie/store-header.tsx","sha256":"
|
|
1
|
+
{"$schema":"https://registry.zappie.ai/v1/schema/item.json","name":"store-header","type":"component","version":"1.0.0","title":"Store header","description":"Show store details, opening status and fulfilment.","requires":{"@zappie/sdk":"^0.1.0","@zappie/tokens":"^0.1.0"},"dependencies":{},"registryDependencies":["open-status"],"files":[{"path":"store-header.tsx","target":"components/zappie/store-header.tsx","sha256":"6fa907b259d33232b80ff34efea83b7a79e783dd3e6741faf8952f26461cbfe3","content":"\"use client\";\n\nimport { OpenStatus } from \"@/components/zappie/open-status\";\nimport { useCart, useStore } from \"@zappie/sdk/react\";\nimport { useCallback, useState } from \"react\";\nimport \"./store-header.css\";\n\nexport type StoreHeaderProps = { branch?: string };\n\nexport function StoreHeader({ branch }: StoreHeaderProps) {\n const store = useStore();\n const cart = useCart();\n if (store.isLoading) {\n return <header className=\"z-kit-store-header\">Loading store…</header>;\n }\n if (store.error || !store.data) {\n return (\n <header className=\"z-kit-store-header\" role=\"alert\">\n Store unavailable\n </header>\n );\n }\n const selected =\n store.data.branches.find(\n (item) => item.id === (branch ?? cart.branchId) || item.slug === branch,\n ) ??\n store.data.branches.find((item) => item.isDefault) ??\n store.data.branches[0];\n return (\n <header className=\"z-kit-store-header\">\n {store.data.brand.coverImageUrl && (\n <img\n className=\"z-kit-store-header__cover\"\n src={store.data.brand.coverImageUrl}\n alt=\"\"\n />\n )}\n <div className=\"z-kit-store-header__identity\">\n {store.data.brand.logoUrl && <Logo src={store.data.brand.logoUrl} />}\n <div>\n <h1>{store.data.name}</h1>\n {store.data.description && <p>{store.data.description}</p>}\n </div>\n </div>\n {selected && (\n <div className=\"z-kit-store-header__summary\">\n <OpenStatus\n branch={selected}\n timezone={selected.timezone ?? store.data.timezone}\n />\n <span>{fulfilment(selected.fulfilment)}</span>\n </div>\n )}\n </header>\n );\n}\n\n// A wide logo is contained so its ends show; a square or tall one covers its box.\nfunction Logo({ src }: { src: string }) {\n const [fit, setFit] = useState<\"contain\" | \"cover\">(\"contain\");\n // A logo loaded before hydration fires no onLoad for React, so the ref reads it too.\n const measure = useCallback((image: HTMLImageElement | null) => {\n if (image?.complete && image.naturalWidth > 0) {\n setFit(image.naturalWidth > image.naturalHeight ? \"contain\" : \"cover\");\n }\n }, []);\n return (\n <img\n ref={measure}\n src={src}\n alt=\"\"\n data-fit={fit}\n onLoad={(event) => measure(event.currentTarget)}\n />\n );\n}\n\nfunction fulfilment(value: {\n pickup: boolean;\n delivery: boolean;\n dineIn: boolean;\n}) {\n return [\n value.delivery ? \"Delivery\" : \"\",\n value.pickup ? \"Pickup\" : \"\",\n value.dineIn ? \"Dine in\" : \"\",\n ]\n .filter(Boolean)\n .join(\" · \");\n}\n"},{"path":"store-header.css","target":"components/zappie/store-header.css","sha256":"b7fad541ad0e9085e97918b1efcdfabd4241a3a5af248fcae61c2abe180f4507","content":".z-kit-store-header {\n display: grid;\n gap: var(--z-space-4);\n color: var(--z-text);\n font-family: var(--z-font-body);\n}\n.z-kit-store-header__cover {\n width: 100%;\n aspect-ratio: 12 / 5;\n max-height: 20rem;\n object-fit: cover;\n border-radius: var(--z-radius-lg);\n}\n.z-kit-store-header__identity,\n.z-kit-store-header__summary {\n display: flex;\n align-items: center;\n gap: var(--z-space-3);\n}\n.z-kit-store-header__identity img {\n width: var(--z-media-sm);\n height: var(--z-media-sm);\n object-fit: cover;\n border-radius: var(--z-radius-md);\n}\n.z-kit-store-header__identity img[data-fit=\"contain\"] {\n object-fit: contain;\n}\n.z-kit-store-header h1 {\n margin: 0;\n font-family: var(--z-font-display);\n}\n.z-kit-store-header p {\n color: var(--z-text-muted);\n}\n.z-kit-store-header__summary {\n justify-content: space-between;\n}\n"}],"tokens":["--z-space-3","--z-space-4","--z-text","--z-text-muted","--z-radius-md","--z-radius-lg","--z-font-body","--z-font-display","--z-media-sm"],"frameworks":["react-router","next","vite","astro-react"],"docs":"https://docs.zappie.ai/kit/store-header"}
|