@snowcone-app/ui 0.4.6 → 0.4.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowcone-app/ui",
3
- "version": "0.4.6",
3
+ "version": "0.4.8",
4
4
  "description": "React components for merchandise visualization and customization",
5
5
  "keywords": [
6
6
  "react",
@@ -103,7 +103,7 @@
103
103
  "react-instantsearch": "^7.15.5",
104
104
  "react-zoom-pan-pinch": "^3.6.4",
105
105
  "tailwind-merge": "^3.0.0",
106
- "@snowcone-app/sdk": "0.20.0"
106
+ "@snowcone-app/sdk": "0.20.1"
107
107
  },
108
108
  "devDependencies": {
109
109
  "@chromatic-com/storybook": "^4.1.2",
@@ -16,6 +16,7 @@ import {
16
16
  import { useShopOptional } from "../patterns/ShopProvider";
17
17
  import { useRealtimeOptional } from "../patterns/RealtimeProvider";
18
18
  import { useMockupPriorityOptional } from "../patterns/MockupPriorityProvider";
19
+ import { useMockupBackgroundOptional } from "../patterns/MockupBackground";
19
20
  import { LoadingOverlayPrismCandyInline } from "../components/LoadingOverlayPrismCandy";
20
21
  import {
21
22
  createDesignForPlacements,
@@ -82,6 +83,8 @@ function buildHeroMockupUrl(args: {
82
83
  mockupId: string;
83
84
  variantId: string;
84
85
  width: number;
86
+ /** Canonical 6-hex background (see MockupBackground) — painted behind alpha scenes. */
87
+ bg?: string;
85
88
  }): string | null {
86
89
  const snow =
87
90
  (typeof window !== "undefined" &&
@@ -97,6 +100,7 @@ function buildHeroMockupUrl(args: {
97
100
  width: args.width,
98
101
  view: args.mockupId,
99
102
  variant: args.variantId,
103
+ ...(args.bg ? { bg: args.bg } : {}),
100
104
  ...(snow.resolver ? { base: snow.resolver } : {}),
101
105
  });
102
106
  }
@@ -204,6 +208,8 @@ export const HeroProductImage = memo(function HeroProductImage({
204
208
  const shopContext = useShopOptional();
205
209
  const realtimeContext = useRealtimeOptional();
206
210
  const priorityContext = useMockupPriorityOptional();
211
+ // Page background baked into static mockup URLs (see MockupBackground).
212
+ const mockupBg = useMockupBackgroundOptional();
207
213
  const priorityContextRef = useRef(priorityContext);
208
214
  priorityContextRef.current = priorityContext;
209
215
 
@@ -295,6 +301,7 @@ export const HeroProductImage = memo(function HeroProductImage({
295
301
  mockupId: actualMockupId,
296
302
  variantId: actualGvid,
297
303
  width,
304
+ bg: mockupBg,
298
305
  });
299
306
  }, [
300
307
  realtimeUrl,
@@ -304,6 +311,7 @@ export const HeroProductImage = memo(function HeroProductImage({
304
311
  actualProductId,
305
312
  actualMockupId,
306
313
  actualGvid,
314
+ mockupBg,
307
315
  product?.placements,
308
316
  context?.product?.placements,
309
317
  images,
@@ -728,6 +736,7 @@ export const HeroProductImage = memo(function HeroProductImage({
728
736
  mockupId: actualMockupId,
729
737
  variantId: actualGvid,
730
738
  width: width,
739
+ bg: mockupBg,
731
740
  });
732
741
  if (!url) return;
733
742
 
@@ -815,6 +824,7 @@ export const HeroProductImage = memo(function HeroProductImage({
815
824
  actualProductId,
816
825
  actualMockupId,
817
826
  actualGvid,
827
+ mockupBg,
818
828
  images,
819
829
  width,
820
830
  effects,
package/src/index.ts CHANGED
@@ -170,6 +170,13 @@ export type {
170
170
  PriorityLevel,
171
171
  } from "./patterns/MockupPriorityProvider";
172
172
 
173
+ // MockupBackground - page background baked into static mockup URLs (bg=)
174
+ export {
175
+ MockupBackground,
176
+ useMockupBackgroundOptional,
177
+ } from "./patterns/MockupBackground";
178
+ export type { MockupBackgroundProps } from "./patterns/MockupBackground";
179
+
173
180
  // ProductPageProvider - Quick-start providers for product pages
174
181
  export {
175
182
  ProductPageProvider,
@@ -0,0 +1,43 @@
1
+ "use client";
2
+
3
+ /**
4
+ * MockupBackground — page-level background color for mockup renders.
5
+ *
6
+ * Wrap a subtree to have every `HeroProductImage` inside it append
7
+ * `bg=<hex>` to the static mockup URLs it builds. The renderer paints the
8
+ * color at the bottom of the stage, so it only shows through mockup scenes
9
+ * whose background has alpha (studio cutouts) — opaque scenes are unaffected.
10
+ *
11
+ * Why: cutout mockups are transparent where the scene ends. Layout tricks
12
+ * that stack a blurred copy of the image behind the sharp one (the PDP
13
+ * edge-blur width extension) bleed through that transparency. Baking the
14
+ * page background into the render makes the image opaque and self-contained
15
+ * (zoom overlays, saved snapshots, and the blur layers all inherit it).
16
+ *
17
+ * `bg` must be the canonical public grammar: lowercase 6-hex, no "#"
18
+ * (URLs are cache keys). Invalid values throw at URL-build time via
19
+ * `getMockupUrl` — loudly, dev and prod.
20
+ */
21
+
22
+ import React, { createContext, useContext } from "react";
23
+
24
+ const MockupBackgroundContext = createContext<string | undefined>(undefined);
25
+
26
+ export interface MockupBackgroundProps {
27
+ /** Canonical background hex (lowercase 6-hex, no "#"), or undefined to leave URLs untouched. */
28
+ bg?: string;
29
+ children: React.ReactNode;
30
+ }
31
+
32
+ export function MockupBackground({ bg, children }: MockupBackgroundProps) {
33
+ return (
34
+ <MockupBackgroundContext.Provider value={bg}>
35
+ {children}
36
+ </MockupBackgroundContext.Provider>
37
+ );
38
+ }
39
+
40
+ /** Background hex from the nearest MockupBackground, or undefined outside one. */
41
+ export function useMockupBackgroundOptional(): string | undefined {
42
+ return useContext(MockupBackgroundContext);
43
+ }
@@ -42,6 +42,7 @@ import type { RealtimeRenderError } from "@snowcone-app/sdk";
42
42
  import { readEnv } from "../lib/env";
43
43
  import { useProductOptional } from "./Product";
44
44
  import type { ReactProductContext } from "./Product";
45
+ import { useMockupBackgroundOptional } from "./MockupBackground";
45
46
 
46
47
  // Default artboard size for export calculations (matches @snowcone-app/canvas default)
47
48
  const DEFAULT_ARTBOARD_SIZE = 2000;
@@ -273,6 +274,9 @@ export function RealtimeProvider({
273
274
  grantUrl,
274
275
  }: RealtimeProviderProps) {
275
276
  const productContext = useProductOptional();
277
+ // Page background for renders (see MockupBackground) — sent on the config so
278
+ // realtime results match static `bg=` mockup URLs.
279
+ const mockupBg = useMockupBackgroundOptional();
276
280
 
277
281
  const [isEnabled, setIsEnabled] = useState(autoEnable);
278
282
 
@@ -305,6 +309,7 @@ export function RealtimeProvider({
305
309
 
306
310
  // Track last sent variantId to detect when we need to re-send config
307
311
  const lastSentVariantIdRef = useRef<string | null>(null);
312
+ const lastSentBgRef = useRef<string | undefined>(undefined);
308
313
 
309
314
  // Track last sent colors to prevent duplicates
310
315
  const lastSentColorsRef = useRef<Record<string, string>>({});
@@ -577,6 +582,7 @@ export function RealtimeProvider({
577
582
  disconnect();
578
583
  configSentRef.current = false;
579
584
  lastSentVariantIdRef.current = null;
585
+ lastSentBgRef.current = undefined;
580
586
  lastSentColorsRef.current = {};
581
587
  };
582
588
  }
@@ -611,8 +617,10 @@ export function RealtimeProvider({
611
617
  const isFirstSend = !configSentRef.current;
612
618
  const variantChanged = lastSentVariantIdRef.current !== null &&
613
619
  lastSentVariantIdRef.current !== variantId;
620
+ // 3. Background changed (theme flip swaps the MockupBackground hex)
621
+ const bgChanged = configSentRef.current && lastSentBgRef.current !== mockupBg;
614
622
 
615
- if (!isFirstSend && !variantChanged) {
623
+ if (!isFirstSend && !variantChanged && !bgChanged) {
616
624
  return; // Nothing changed, skip
617
625
  }
618
626
 
@@ -637,6 +645,7 @@ export function RealtimeProvider({
637
645
  variantId,
638
646
  shop: shop as string,
639
647
  width: mockupWidth,
648
+ ...(mockupBg ? { bg: mockupBg } : {}),
640
649
  placementSettings: { ...placementSettingsOverridesRef.current },
641
650
  }
642
651
  : {
@@ -646,6 +655,7 @@ export function RealtimeProvider({
646
655
  shop: "hTidnpNnNa",
647
656
  seal: "bypass-sig-for-k6-load-test",
648
657
  width: mockupWidth,
658
+ ...(mockupBg ? { bg: mockupBg } : {}),
649
659
  placementSettings: { ...placementSettingsOverridesRef.current },
650
660
  };
651
661
 
@@ -662,6 +672,7 @@ export function RealtimeProvider({
662
672
 
663
673
  configSentRef.current = true;
664
674
  lastSentVariantIdRef.current = variantId;
675
+ lastSentBgRef.current = mockupBg;
665
676
 
666
677
  // Send default colors for color placements
667
678
  setTimeout(() => {
@@ -682,6 +693,7 @@ export function RealtimeProvider({
682
693
  sendConfigRaw,
683
694
  sendColorBlobTracked,
684
695
  mockupWidth,
696
+ mockupBg,
685
697
  ]);
686
698
 
687
699
  // Watch for color placement changes