@shopbb/helium 0.1.0 → 0.3.0-alpha.1

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 (46) hide show
  1. package/dist/client.d.ts +48 -0
  2. package/dist/client.d.ts.map +1 -0
  3. package/dist/client.js +64 -0
  4. package/dist/client.js.map +1 -0
  5. package/dist/components/AddToCartButton.d.ts +41 -0
  6. package/dist/components/AddToCartButton.d.ts.map +1 -0
  7. package/dist/components/AddToCartButton.js +51 -0
  8. package/dist/components/AddToCartButton.js.map +1 -0
  9. package/dist/components/CartLineQuantityAdjustButton.d.ts +40 -0
  10. package/dist/components/CartLineQuantityAdjustButton.d.ts.map +1 -0
  11. package/dist/components/CartLineQuantityAdjustButton.js +58 -0
  12. package/dist/components/CartLineQuantityAdjustButton.js.map +1 -0
  13. package/dist/components/Image.d.ts +39 -0
  14. package/dist/components/Image.d.ts.map +1 -0
  15. package/dist/components/Image.js +28 -0
  16. package/dist/components/Image.js.map +1 -0
  17. package/dist/components/Money.d.ts +41 -0
  18. package/dist/components/Money.d.ts.map +1 -0
  19. package/dist/components/Money.js +48 -0
  20. package/dist/components/Money.js.map +1 -0
  21. package/dist/components/ProductPrice.d.ts +28 -0
  22. package/dist/components/ProductPrice.d.ts.map +1 -0
  23. package/dist/components/ProductPrice.js +10 -0
  24. package/dist/components/ProductPrice.js.map +1 -0
  25. package/dist/components/VariantSelector.d.ts +80 -0
  26. package/dist/components/VariantSelector.d.ts.map +1 -0
  27. package/dist/components/VariantSelector.js +87 -0
  28. package/dist/components/VariantSelector.js.map +1 -0
  29. package/dist/components/index.d.ts +24 -0
  30. package/dist/components/index.d.ts.map +1 -0
  31. package/dist/components/index.js +18 -0
  32. package/dist/components/index.js.map +1 -0
  33. package/dist/react.d.ts +98 -0
  34. package/dist/react.d.ts.map +1 -0
  35. package/dist/react.js +168 -0
  36. package/dist/react.js.map +1 -0
  37. package/package.json +33 -3
  38. package/src/client.tsx +103 -0
  39. package/src/components/AddToCartButton.tsx +90 -0
  40. package/src/components/CartLineQuantityAdjustButton.tsx +119 -0
  41. package/src/components/Image.tsx +93 -0
  42. package/src/components/Money.tsx +106 -0
  43. package/src/components/ProductPrice.tsx +61 -0
  44. package/src/components/VariantSelector.tsx +148 -0
  45. package/src/components/index.ts +33 -0
  46. package/src/react.tsx +296 -0
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @shopbb/helium/client — Browser-side hydration helpers
3
+ *
4
+ * 商家的客户端 entry:
5
+ *
6
+ * import { hydrate } from '@shopbb/helium/client';
7
+ * import { App } from './app';
8
+ *
9
+ * hydrate(<App />);
10
+ *
11
+ * 行为:
12
+ * - 从 window.__HELIUM__ 拿 SSR 时塞进去的 boot data
13
+ * - 包一层 BrowserHeliumContextProvider(含 storefront.query helper,纯客户端 fetch)
14
+ * - 调 hydrateRoot
15
+ */
16
+ import * as React from 'react';
17
+ interface ClientStorefrontClient {
18
+ query<T = any>(graphql: string, options?: {
19
+ variables?: any;
20
+ }): Promise<T>;
21
+ mutate<T = any>(graphql: string, options?: {
22
+ variables?: any;
23
+ }): Promise<T>;
24
+ }
25
+ interface ClientHeliumContext {
26
+ storefront: ClientStorefrontClient;
27
+ url: string;
28
+ }
29
+ declare global {
30
+ interface Window {
31
+ __HELIUM__?: {
32
+ storefront: {
33
+ apiUrl: string;
34
+ publicAccessToken: string;
35
+ storeId: string;
36
+ };
37
+ url: string;
38
+ };
39
+ }
40
+ }
41
+ export declare function useHeliumClient(): ClientHeliumContext;
42
+ /**
43
+ * 客户端 hydrate 入口。
44
+ * 通常调用一次即可。
45
+ */
46
+ export declare function hydrate(app: React.ReactElement): void;
47
+ export {};
48
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAS/B,UAAU,sBAAsB;IAC9B,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC3E,MAAM,CAAC,CAAC,GAAG,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,GAAG,CAAA;KAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC7E;AAED,UAAU,mBAAmB;IAC3B,UAAU,EAAE,sBAAsB,CAAC;IACnC,GAAG,EAAE,MAAM,CAAC;CACb;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,UAAU,CAAC,EAAE;YACX,UAAU,EAAE;gBAAE,MAAM,EAAE,MAAM,CAAC;gBAAC,iBAAiB,EAAE,MAAM,CAAC;gBAAC,OAAO,EAAE,MAAM,CAAA;aAAE,CAAC;YAC3E,GAAG,EAAE,MAAM,CAAC;SACb,CAAC;KACH;CACF;AAID,wBAAgB,eAAe,IAAI,mBAAmB,CAQrD;AA4BD;;;GAGG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,YAAY,QAgB9C"}
package/dist/client.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @shopbb/helium/client — Browser-side hydration helpers
3
+ *
4
+ * 商家的客户端 entry:
5
+ *
6
+ * import { hydrate } from '@shopbb/helium/client';
7
+ * import { App } from './app';
8
+ *
9
+ * hydrate(<App />);
10
+ *
11
+ * 行为:
12
+ * - 从 window.__HELIUM__ 拿 SSR 时塞进去的 boot data
13
+ * - 包一层 BrowserHeliumContextProvider(含 storefront.query helper,纯客户端 fetch)
14
+ * - 调 hydrateRoot
15
+ */
16
+ import * as React from 'react';
17
+ import { hydrateRoot } from 'react-dom/client';
18
+ const ClientHeliumReactContext = React.createContext(null);
19
+ export function useHeliumClient() {
20
+ const ctx = React.useContext(ClientHeliumReactContext);
21
+ if (!ctx) {
22
+ throw new Error('useHeliumClient() must be called within HeliumClientProvider (use hydrate())');
23
+ }
24
+ return ctx;
25
+ }
26
+ function makeClientStorefront(boot) {
27
+ async function call(graphql, variables) {
28
+ const res = await fetch(boot.apiUrl, {
29
+ method: 'POST',
30
+ headers: {
31
+ 'Content-Type': 'application/json',
32
+ 'X-Storefront-Access-Token': boot.publicAccessToken,
33
+ },
34
+ body: JSON.stringify({ query: graphql, variables }),
35
+ });
36
+ const json = (await res.json());
37
+ if (json.errors) {
38
+ throw new Error(json.errors[0]?.message || 'GraphQL error');
39
+ }
40
+ return json.data;
41
+ }
42
+ return {
43
+ query: (q, opts) => call(q, opts?.variables),
44
+ mutate: (q, opts) => call(q, opts?.variables),
45
+ };
46
+ }
47
+ /**
48
+ * 客户端 hydrate 入口。
49
+ * 通常调用一次即可。
50
+ */
51
+ export function hydrate(app) {
52
+ const boot = window.__HELIUM__;
53
+ if (!boot) {
54
+ console.warn('[helium] window.__HELIUM__ missing, hydrate skipped');
55
+ return;
56
+ }
57
+ const ctx = {
58
+ storefront: makeClientStorefront(boot.storefront),
59
+ url: boot.url,
60
+ };
61
+ const wrapped = React.createElement(ClientHeliumReactContext.Provider, { value: ctx }, app);
62
+ hydrateRoot(document.getElementById('root'), wrapped);
63
+ }
64
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AA2B/C,MAAM,wBAAwB,GAAG,KAAK,CAAC,aAAa,CAA6B,IAAI,CAAC,CAAC;AAEvF,MAAM,UAAU,eAAe;IAC7B,MAAM,GAAG,GAAG,KAAK,CAAC,UAAU,CAAC,wBAAwB,CAAC,CAAC;IACvD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,8EAA8E,CAC/E,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,oBAAoB,CAAC,IAI7B;IACC,KAAK,UAAU,IAAI,CAAI,OAAe,EAAE,SAAe;QACrD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE;YACnC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,2BAA2B,EAAE,IAAI,CAAC,iBAAiB;aACpD;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;SACpD,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAsD,CAAC;QACrF,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,eAAe,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,IAAI,CAAC,IAAS,CAAC;IACxB,CAAC;IACD,OAAO;QACL,KAAK,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC;QAC5C,MAAM,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC;KAC9C,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,GAAuB;IAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC;IAC/B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC;QACpE,OAAO;IACT,CAAC;IACD,MAAM,GAAG,GAAwB;QAC/B,UAAU,EAAE,oBAAoB,CAAC,IAAI,CAAC,UAAU,CAAC;QACjD,GAAG,EAAE,IAAI,CAAC,GAAG;KACd,CAAC;IACF,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa,CACjC,wBAAwB,CAAC,QAAQ,EACjC,EAAE,KAAK,EAAE,GAAG,EAAE,EACd,GAAG,CACJ,CAAC;IACF,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAE,EAAE,OAAO,CAAC,CAAC;AACzD,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * <AddToCartButton> — 加入购物车按钮
3
+ *
4
+ * 对齐 @shopify/hydrogen <AddToCartButton>:
5
+ * - 内置 loading 态、disabled、error 处理
6
+ * - 支持 onAdd 回调(实际加购逻辑由外部 cart hook 提供)
7
+ * - 触发 analytics 事件钩子
8
+ * - 不带样式,商家自己控制
9
+ *
10
+ * 用法(Phase 1,需要传 onAdd):
11
+ * const { addLine } = useCart();
12
+ * <AddToCartButton
13
+ * variantId={selectedVariant.id}
14
+ * quantity={1}
15
+ * disabled={!selectedVariant.availableForSale}
16
+ * onAdd={(vid, qty) => addLine(vid, qty)}
17
+ * >
18
+ * 加入购物车
19
+ * </AddToCartButton>
20
+ *
21
+ * Phase 2 后:组件会自动从 <CartProvider> 拿,无需 onAdd 也能用。
22
+ */
23
+ import * as React from 'react';
24
+ export interface AddToCartButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onClick' | 'onError'> {
25
+ /** 必传:variant 的 GID(gid://shopbb/ProductVariant/...) */
26
+ variantId: string;
27
+ /** 数量,默认 1 */
28
+ quantity?: number;
29
+ /** 实际加购回调。Phase 1 必传;Phase 2 后可从 Provider 自动获取 */
30
+ onAdd?: (variantId: string, quantity: number) => Promise<void>;
31
+ /** 加购成功回调(如导航到 /cart) */
32
+ onAdded?: () => void;
33
+ /** 加购失败回调 */
34
+ onError?: (err: Error) => void;
35
+ /** 加购中显示的文本,默认 "加入中..." */
36
+ loadingText?: React.ReactNode;
37
+ /** 不可用时显示的文本,默认 "缺货" */
38
+ unavailableText?: React.ReactNode;
39
+ }
40
+ export declare function AddToCartButton(props: AddToCartButtonProps): import("react/jsx-runtime").JSX.Element;
41
+ //# sourceMappingURL=AddToCartButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AddToCartButton.d.ts","sourceRoot":"","sources":["../../src/components/AddToCartButton.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IACtH,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kDAAkD;IAClD,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,yBAAyB;IACzB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,aAAa;IACb,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAC/B,2BAA2B;IAC3B,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,wBAAwB;IACxB,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACnC;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,2CA+C1D"}
@@ -0,0 +1,51 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * <AddToCartButton> — 加入购物车按钮
4
+ *
5
+ * 对齐 @shopify/hydrogen <AddToCartButton>:
6
+ * - 内置 loading 态、disabled、error 处理
7
+ * - 支持 onAdd 回调(实际加购逻辑由外部 cart hook 提供)
8
+ * - 触发 analytics 事件钩子
9
+ * - 不带样式,商家自己控制
10
+ *
11
+ * 用法(Phase 1,需要传 onAdd):
12
+ * const { addLine } = useCart();
13
+ * <AddToCartButton
14
+ * variantId={selectedVariant.id}
15
+ * quantity={1}
16
+ * disabled={!selectedVariant.availableForSale}
17
+ * onAdd={(vid, qty) => addLine(vid, qty)}
18
+ * >
19
+ * 加入购物车
20
+ * </AddToCartButton>
21
+ *
22
+ * Phase 2 后:组件会自动从 <CartProvider> 拿,无需 onAdd 也能用。
23
+ */
24
+ import * as React from 'react';
25
+ export function AddToCartButton(props) {
26
+ const { variantId, quantity = 1, onAdd, onAdded, onError, loadingText = '加入中...', unavailableText = '缺货', children = '加入购物车', disabled: disabledProp, ...rest } = props;
27
+ const [adding, setAdding] = React.useState(false);
28
+ const handleClick = async (e) => {
29
+ e.preventDefault();
30
+ if (adding || disabledProp)
31
+ return;
32
+ if (!onAdd) {
33
+ console.warn('[AddToCartButton] onAdd not provided; this button does nothing in Phase 1');
34
+ return;
35
+ }
36
+ setAdding(true);
37
+ try {
38
+ await onAdd(variantId, quantity);
39
+ onAdded?.();
40
+ }
41
+ catch (err) {
42
+ console.error('[AddToCartButton] add failed:', err);
43
+ onError?.(err instanceof Error ? err : new Error(String(err)));
44
+ }
45
+ finally {
46
+ setAdding(false);
47
+ }
48
+ };
49
+ return (_jsx("button", { type: "button", ...rest, "data-add-to-cart": true, "data-loading": adding ? '' : undefined, disabled: disabledProp || adding, onClick: handleClick, children: adding ? loadingText : disabledProp ? unavailableText : children }));
50
+ }
51
+ //# sourceMappingURL=AddToCartButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AddToCartButton.js","sourceRoot":"","sources":["../../src/components/AddToCartButton.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAmB/B,MAAM,UAAU,eAAe,CAAC,KAA2B;IACzD,MAAM,EACJ,SAAS,EACT,QAAQ,GAAG,CAAC,EACZ,KAAK,EACL,OAAO,EACP,OAAO,EACP,WAAW,GAAG,QAAQ,EACtB,eAAe,GAAG,IAAI,EACtB,QAAQ,GAAG,OAAO,EAClB,QAAQ,EAAE,YAAY,EACtB,GAAG,IAAI,EACR,GAAG,KAAK,CAAC;IAEV,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAElD,MAAM,WAAW,GAAG,KAAK,EAAE,CAAsC,EAAE,EAAE;QACnE,CAAC,CAAC,cAAc,EAAE,CAAC;QACnB,IAAI,MAAM,IAAI,YAAY;YAAE,OAAO;QACnC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,2EAA2E,CAAC,CAAC;YAC1F,OAAO;QACT,CAAC;QACD,SAAS,CAAC,IAAI,CAAC,CAAC;QAChB,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YACjC,OAAO,EAAE,EAAE,CAAC;QACd,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,+BAA+B,EAAE,GAAG,CAAC,CAAC;YACpD,OAAO,EAAE,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACjE,CAAC;gBAAS,CAAC;YACT,SAAS,CAAC,KAAK,CAAC,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,CACL,iBACE,IAAI,EAAC,QAAQ,KACT,IAAI,4CAEM,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,EACrC,QAAQ,EAAE,YAAY,IAAI,MAAM,EAChC,OAAO,EAAE,WAAW,YAEnB,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,GAC1D,CACV,CAAC;AACJ,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * <CartLineQuantityAdjustButton> — cart 行数量加减按钮
3
+ *
4
+ * 对齐 @shopify/hydrogen <CartLineQuantityAdjustButton>:
5
+ * - +/- 按钮调用 onChange
6
+ * - 防抖(300ms)避免点快导致多次请求
7
+ * - min/max 校验
8
+ * - 移除时弹确认(可选)
9
+ *
10
+ * 用法:
11
+ * const { updateLine, removeLine } = useCart();
12
+ * <CartLineQuantityAdjustButton
13
+ * line={line}
14
+ * onUpdate={(newQty) => updateLine(line.id, newQty)}
15
+ * onRemove={() => removeLine(line.id)}
16
+ * />
17
+ */
18
+ import * as React from 'react';
19
+ export interface CartLineQuantityAdjustButtonProps {
20
+ /** line 的当前数量 */
21
+ quantity: number;
22
+ /** 最小值,默认 1(小于此值改成 remove) */
23
+ min?: number;
24
+ /** 最大值,默认 99 */
25
+ max?: number;
26
+ /** 数量变化回调 */
27
+ onUpdate?: (newQuantity: number) => Promise<void> | void;
28
+ /** 数量降到 0 时触发 remove */
29
+ onRemove?: () => Promise<void> | void;
30
+ /** 防抖延迟(ms),默认 300 */
31
+ debounceMs?: number;
32
+ /** 自定义包装容器 className */
33
+ className?: string;
34
+ /** 减号按钮 props 透传 */
35
+ decreaseProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
36
+ /** 加号按钮 props 透传 */
37
+ increaseProps?: React.ButtonHTMLAttributes<HTMLButtonElement>;
38
+ }
39
+ export declare function CartLineQuantityAdjustButton(props: CartLineQuantityAdjustButtonProps): import("react/jsx-runtime").JSX.Element;
40
+ //# sourceMappingURL=CartLineQuantityAdjustButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CartLineQuantityAdjustButton.d.ts","sourceRoot":"","sources":["../../src/components/CartLineQuantityAdjustButton.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,iCAAiC;IAChD,iBAAiB;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa;IACb,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD,wBAAwB;IACxB,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACtC,sBAAsB;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB;IACpB,aAAa,CAAC,EAAE,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;IAC9D,oBAAoB;IACpB,aAAa,CAAC,EAAE,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;CAC/D;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,iCAAiC,2CA6EpF"}
@@ -0,0 +1,58 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * <CartLineQuantityAdjustButton> — cart 行数量加减按钮
4
+ *
5
+ * 对齐 @shopify/hydrogen <CartLineQuantityAdjustButton>:
6
+ * - +/- 按钮调用 onChange
7
+ * - 防抖(300ms)避免点快导致多次请求
8
+ * - min/max 校验
9
+ * - 移除时弹确认(可选)
10
+ *
11
+ * 用法:
12
+ * const { updateLine, removeLine } = useCart();
13
+ * <CartLineQuantityAdjustButton
14
+ * line={line}
15
+ * onUpdate={(newQty) => updateLine(line.id, newQty)}
16
+ * onRemove={() => removeLine(line.id)}
17
+ * />
18
+ */
19
+ import * as React from 'react';
20
+ export function CartLineQuantityAdjustButton(props) {
21
+ const { quantity, min = 1, max = 99, onUpdate, onRemove, debounceMs = 300, className, decreaseProps, increaseProps, } = props;
22
+ // optimistic 显示的 quantity
23
+ const [optimistic, setOptimistic] = React.useState(quantity);
24
+ React.useEffect(() => setOptimistic(quantity), [quantity]);
25
+ const [pending, setPending] = React.useState(false);
26
+ const timerRef = React.useRef(null);
27
+ const flushUpdate = React.useCallback((target) => {
28
+ if (timerRef.current)
29
+ clearTimeout(timerRef.current);
30
+ timerRef.current = setTimeout(async () => {
31
+ setPending(true);
32
+ try {
33
+ if (target < min) {
34
+ await onRemove?.();
35
+ }
36
+ else {
37
+ await onUpdate?.(target);
38
+ }
39
+ }
40
+ finally {
41
+ setPending(false);
42
+ }
43
+ }, debounceMs);
44
+ }, [min, onRemove, onUpdate, debounceMs]);
45
+ const handleAdjust = (delta) => {
46
+ const next = optimistic + delta;
47
+ if (next > max)
48
+ return;
49
+ setOptimistic(next);
50
+ flushUpdate(next);
51
+ };
52
+ React.useEffect(() => () => {
53
+ if (timerRef.current)
54
+ clearTimeout(timerRef.current);
55
+ }, []);
56
+ return (_jsxs("div", { className: className, "data-quantity-adjust": true, style: { display: 'inline-flex', alignItems: 'center' }, children: [_jsx("button", { type: "button", ...decreaseProps, "aria-label": "\u51CF\u5C11", "data-quantity-decrease": true, onClick: () => handleAdjust(-1), disabled: pending && optimistic <= min, children: decreaseProps?.children ?? '−' }), _jsx("span", { "data-quantity-value": true, "aria-live": "polite", style: { margin: '0 0.5em', minWidth: '1.5em', textAlign: 'center' }, children: optimistic }), _jsx("button", { type: "button", ...increaseProps, "aria-label": "\u589E\u52A0", "data-quantity-increase": true, onClick: () => handleAdjust(1), disabled: pending && optimistic >= max, children: increaseProps?.children ?? '+' })] }));
57
+ }
58
+ //# sourceMappingURL=CartLineQuantityAdjustButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CartLineQuantityAdjustButton.js","sourceRoot":"","sources":["../../src/components/CartLineQuantityAdjustButton.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * <Image> — 响应式商品图片
3
+ *
4
+ * 对齐 @shopify/hydrogen 的 <Image>:
5
+ * - 自动生成 srcset(5 个分辨率档:375 / 750 / 1024 / 1536 / 1920)
6
+ * - sizes 媒体查询
7
+ * - loading="lazy" 默认(首屏可关)
8
+ * - decoding="async"
9
+ * - aspectRatio 占位(不抖)
10
+ * - 自动 alt
11
+ *
12
+ * srcset 里的 url 通过 ?width= query 表达,服务端可忽略或接 cdn-cgi/image。
13
+ *
14
+ * 用法:
15
+ * <Image data={{ url, altText, width, height }} sizes="(min-width: 768px) 50vw, 100vw" />
16
+ * <Image data={...} aspectRatio="1/1" />
17
+ * <Image data={...} loading="eager" /> // 首屏
18
+ */
19
+ import * as React from 'react';
20
+ export interface ImageData {
21
+ url: string;
22
+ altText?: string | null;
23
+ width?: number | null;
24
+ height?: number | null;
25
+ }
26
+ export interface ImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'src' | 'srcSet' | 'loading'> {
27
+ /** 必传:图片对象 */
28
+ data: ImageData;
29
+ /** 媒体查询 sizes,例如 (min-width: 768px) 50vw, 100vw */
30
+ sizes?: string;
31
+ /** 强制宽高比,例如 '1/1' '4/3' '16/9' */
32
+ aspectRatio?: string;
33
+ /** lazy(默认) / eager(首屏图用) */
34
+ loading?: 'lazy' | 'eager';
35
+ /** 自定义 srcset 档位 */
36
+ widths?: number[];
37
+ }
38
+ export declare function Image(props: ImageProps): import("react/jsx-runtime").JSX.Element;
39
+ //# sourceMappingURL=Image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Image.d.ts","sourceRoot":"","sources":["../../src/components/Image.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,UAAW,SAAQ,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC/G,cAAc;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,mDAAmD;IACnD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,oBAAoB;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAkBD,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,2CAmCtC"}
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // 默认 srcset 档位
3
+ const DEFAULT_WIDTHS = [375, 750, 1024, 1536, 1920];
4
+ /** 给 url 加 ?width=N query */
5
+ function withWidth(url, width) {
6
+ try {
7
+ const u = new URL(url);
8
+ u.searchParams.set('width', String(width));
9
+ return u.toString();
10
+ }
11
+ catch {
12
+ // 不是绝对 URL,按相对路径处理
13
+ const sep = url.includes('?') ? '&' : '?';
14
+ return `${url}${sep}width=${width}`;
15
+ }
16
+ }
17
+ export function Image(props) {
18
+ const { data, sizes, aspectRatio, loading = 'lazy', widths = DEFAULT_WIDTHS, style, alt: altProp, ...rest } = props;
19
+ const srcset = widths.map((w) => `${withWidth(data.url, w)} ${w}w`).join(', ');
20
+ // src 用中间档位
21
+ const src = withWidth(data.url, widths[Math.floor(widths.length / 2)]);
22
+ const finalStyle = {
23
+ ...(aspectRatio ? { aspectRatio, objectFit: 'cover' } : null),
24
+ ...style,
25
+ };
26
+ return (_jsx("img", { ...rest, src: src, srcSet: srcset, sizes: sizes, alt: altProp ?? data.altText ?? '', loading: loading, decoding: "async", width: data.width || undefined, height: data.height || undefined, style: finalStyle }));
27
+ }
28
+ //# sourceMappingURL=Image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Image.js","sourceRoot":"","sources":["../../src/components/Image.tsx"],"names":[],"mappings":";AAyCA,eAAe;AACf,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAEpD,6BAA6B;AAC7B,SAAS,SAAS,CAAC,GAAW,EAAE,KAAa;IAC3C,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3C,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,mBAAmB;QACnB,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QAC1C,OAAO,GAAG,GAAG,GAAG,GAAG,SAAS,KAAK,EAAE,CAAC;IACtC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,KAAiB;IACrC,MAAM,EACJ,IAAI,EACJ,KAAK,EACL,WAAW,EACX,OAAO,GAAG,MAAM,EAChB,MAAM,GAAG,cAAc,EACvB,KAAK,EACL,GAAG,EAAE,OAAO,EACZ,GAAG,IAAI,EACR,GAAG,KAAK,CAAC;IAEV,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/E,YAAY;IACZ,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAEvE,MAAM,UAAU,GAAwB;QACtC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,OAAgB,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACtE,GAAG,KAAK;KACT,CAAC;IAEF,OAAO,CACL,iBACM,IAAI,EACR,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,OAAO,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,EAClC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAC,OAAO,EAChB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,SAAS,EAC9B,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,SAAS,EAChC,KAAK,EAAE,UAAU,GACjB,CACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * <Money> — 货币格式化
3
+ *
4
+ * 对齐 @shopify/hydrogen 的 <Money>。
5
+ * - 自动按 currencyCode + locale 用 Intl.NumberFormat 格式化
6
+ * - 支持 withoutCurrency / withoutTrailingZeros
7
+ * - 支持 as 渲染成任意元素(默认 <span>)
8
+ * - 支持 measurement(unit price,例如 ¥9.99/100g)
9
+ *
10
+ * 用法:
11
+ * <Money data={{ amount: '9.99', currencyCode: 'CNY' }} />
12
+ * <Money data={{ ... }} withoutCurrency />
13
+ * <Money data={{ ... }} as="div" measurement={{ referenceUnit: 'kg', quantity: 0.1 }} />
14
+ */
15
+ import * as React from 'react';
16
+ export interface MoneyData {
17
+ amount: string;
18
+ currencyCode: string;
19
+ }
20
+ export interface MoneyMeasurement {
21
+ /** 比如 'kg' / '100g' / 'l' */
22
+ referenceUnit: string;
23
+ /** 比如 0.1 表示每 100g */
24
+ quantity?: number;
25
+ }
26
+ export interface MoneyProps extends React.HTMLAttributes<HTMLElement> {
27
+ /** 必传:金额对象 */
28
+ data: MoneyData;
29
+ /** 不显示货币符号,只显示数字 */
30
+ withoutCurrency?: boolean;
31
+ /** 整数时不显示 .00 */
32
+ withoutTrailingZeros?: boolean;
33
+ /** locale 字符串(zh-CN / en-US / ...)。默认按 currencyCode 推断 */
34
+ locale?: string;
35
+ /** 渲染元素,默认 span */
36
+ as?: keyof JSX.IntrinsicElements;
37
+ /** unit price:渲染 ¥99.00/100g */
38
+ measurement?: MoneyMeasurement;
39
+ }
40
+ export declare function Money(props: MoneyProps): import("react/jsx-runtime").JSX.Element;
41
+ //# sourceMappingURL=Money.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Money.d.ts","sourceRoot":"","sources":["../../src/components/Money.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,6BAA6B;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,sBAAsB;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAW,SAAQ,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC;IACnE,cAAc;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,oBAAoB;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iBAAiB;IACjB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mBAAmB;IACnB,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,iBAAiB,CAAC;IACjC,gCAAgC;IAChC,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC;AAYD,wBAAgB,KAAK,CAAC,KAAK,EAAE,UAAU,2CAmDtC"}
@@ -0,0 +1,48 @@
1
+ import { jsxs as _jsxs } from "react/jsx-runtime";
2
+ // currencyCode → 默认 locale 推断
3
+ const DEFAULT_LOCALE_BY_CURRENCY = {
4
+ CNY: 'zh-CN',
5
+ USD: 'en-US',
6
+ EUR: 'de-DE',
7
+ GBP: 'en-GB',
8
+ JPY: 'ja-JP',
9
+ KRW: 'ko-KR',
10
+ };
11
+ export function Money(props) {
12
+ const { data, withoutCurrency, withoutTrailingZeros, locale: localeProp, as, measurement, ...rest } = props;
13
+ const Tag = as || 'span';
14
+ const locale = localeProp || DEFAULT_LOCALE_BY_CURRENCY[data.currencyCode] || 'en-US';
15
+ const amount = Number(data.amount);
16
+ let formatted;
17
+ try {
18
+ const opts = withoutCurrency
19
+ ? {
20
+ minimumFractionDigits: withoutTrailingZeros && Number.isInteger(amount) ? 0 : 2,
21
+ maximumFractionDigits: 2,
22
+ }
23
+ : {
24
+ style: 'currency',
25
+ currency: data.currencyCode,
26
+ minimumFractionDigits: withoutTrailingZeros && Number.isInteger(amount) ? 0 : 2,
27
+ maximumFractionDigits: 2,
28
+ };
29
+ formatted = new Intl.NumberFormat(locale, opts).format(amount);
30
+ }
31
+ catch {
32
+ // Fallback:Intl 不支持的 currencyCode
33
+ formatted = withoutCurrency ? amount.toFixed(2) : `${data.currencyCode} ${amount.toFixed(2)}`;
34
+ }
35
+ // measurement suffix(unit price)
36
+ let suffix = '';
37
+ if (measurement) {
38
+ const q = measurement.quantity ?? 1;
39
+ if (q === 1) {
40
+ suffix = `/${measurement.referenceUnit}`;
41
+ }
42
+ else {
43
+ suffix = `/${q}${measurement.referenceUnit}`;
44
+ }
45
+ }
46
+ return (_jsxs(Tag, { ...rest, "data-money": data.amount, "data-money-currency": data.currencyCode, children: [formatted, suffix] }));
47
+ }
48
+ //# sourceMappingURL=Money.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Money.js","sourceRoot":"","sources":["../../src/components/Money.tsx"],"names":[],"mappings":";AA4CA,8BAA8B;AAC9B,MAAM,0BAA0B,GAA2B;IACzD,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,OAAO;CACb,CAAC;AAEF,MAAM,UAAU,KAAK,CAAC,KAAiB;IACrC,MAAM,EACJ,IAAI,EACJ,eAAe,EACf,oBAAoB,EACpB,MAAM,EAAE,UAAU,EAClB,EAAE,EACF,WAAW,EACX,GAAG,IAAI,EACR,GAAG,KAAK,CAAC;IAEV,MAAM,GAAG,GAAQ,EAAE,IAAI,MAAM,CAAC;IAC9B,MAAM,MAAM,GAAG,UAAU,IAAI,0BAA0B,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC;IACtF,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAEnC,IAAI,SAAiB,CAAC;IACtB,IAAI,CAAC;QACH,MAAM,IAAI,GAA6B,eAAe;YACpD,CAAC,CAAC;gBACE,qBAAqB,EAAE,oBAAoB,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC/E,qBAAqB,EAAE,CAAC;aACzB;YACH,CAAC,CAAC;gBACE,KAAK,EAAE,UAAU;gBACjB,QAAQ,EAAE,IAAI,CAAC,YAAY;gBAC3B,qBAAqB,EAAE,oBAAoB,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC/E,qBAAqB,EAAE,CAAC;aACzB,CAAC;QACN,SAAS,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,kCAAkC;QAClC,SAAS,GAAG,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAChG,CAAC;IAED,iCAAiC;IACjC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,CAAC,GAAG,WAAW,CAAC,QAAQ,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACZ,MAAM,GAAG,IAAI,WAAW,CAAC,aAAa,EAAE,CAAC;QAC3C,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,IAAI,CAAC,GAAG,WAAW,CAAC,aAAa,EAAE,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,OAAO,CACL,MAAC,GAAG,OAAK,IAAI,gBAAc,IAAI,CAAC,MAAM,yBAAuB,IAAI,CAAC,YAAY,aAC3E,SAAS,EACT,MAAM,IACH,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * <ProductPrice> — 商品价格
3
+ *
4
+ * 对齐 @shopify/hydrogen <ProductPrice>:
5
+ * - 渲染当前价
6
+ * - 当 compareAtPrice > price 时自动显示划线对比价
7
+ * - 支持 unit price 模式
8
+ * - 可定制 className / 容器
9
+ *
10
+ * 用法:
11
+ * <ProductPrice price={{ amount: '99', currencyCode: 'CNY' }} />
12
+ * <ProductPrice price={...} compareAtPrice={{ amount: '129', ... }} />
13
+ */
14
+ import * as React from 'react';
15
+ import { type MoneyData, type MoneyMeasurement } from './Money';
16
+ export interface ProductPriceProps extends React.HTMLAttributes<HTMLElement> {
17
+ price: MoneyData;
18
+ compareAtPrice?: MoneyData | null;
19
+ /** unit price,例如每 100g */
20
+ measurement?: MoneyMeasurement;
21
+ /** locale 透传 */
22
+ locale?: string;
23
+ /** 划线价的额外 className */
24
+ compareAtClassName?: string;
25
+ as?: keyof JSX.IntrinsicElements;
26
+ }
27
+ export declare function ProductPrice(props: ProductPriceProps): import("react/jsx-runtime").JSX.Element;
28
+ //# sourceMappingURL=ProductPrice.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProductPrice.d.ts","sourceRoot":"","sources":["../../src/components/ProductPrice.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAS,KAAK,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEvE,MAAM,WAAW,iBAAkB,SAAQ,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC;IAC1E,KAAK,EAAE,SAAS,CAAC;IACjB,cAAc,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAClC,0BAA0B;IAC1B,WAAW,CAAC,EAAE,gBAAgB,CAAC;IAC/B,gBAAgB;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uBAAuB;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,iBAAiB,CAAC;CAClC;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,2CA+BpD"}
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Money } from './Money';
3
+ export function ProductPrice(props) {
4
+ const { price, compareAtPrice, measurement, locale, compareAtClassName, as, ...rest } = props;
5
+ const Tag = as || 'div';
6
+ const hasDiscount = compareAtPrice &&
7
+ Number(compareAtPrice.amount) > Number(price.amount);
8
+ return (_jsxs(Tag, { ...rest, "data-product-price": true, children: [_jsx(Money, { data: price, locale: locale, measurement: measurement }), hasDiscount && compareAtPrice && (_jsx(Money, { data: compareAtPrice, locale: locale, as: "s", className: compareAtClassName, "data-compare-at": "true", style: { marginLeft: '0.5em', opacity: 0.6 } }))] }));
9
+ }
10
+ //# sourceMappingURL=ProductPrice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProductPrice.js","sourceRoot":"","sources":["../../src/components/ProductPrice.tsx"],"names":[],"mappings":";AAeA,OAAO,EAAE,KAAK,EAAyC,MAAM,SAAS,CAAC;AAcvE,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,EACJ,KAAK,EACL,cAAc,EACd,WAAW,EACX,MAAM,EACN,kBAAkB,EAClB,EAAE,EACF,GAAG,IAAI,EACR,GAAG,KAAK,CAAC;IAEV,MAAM,GAAG,GAAQ,EAAE,IAAI,KAAK,CAAC;IAC7B,MAAM,WAAW,GACf,cAAc;QACd,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAEvD,OAAO,CACL,MAAC,GAAG,OAAK,IAAI,yCACX,KAAC,KAAK,IAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,GAAI,EAC/D,WAAW,IAAI,cAAc,IAAI,CAChC,KAAC,KAAK,IACJ,IAAI,EAAE,cAAc,EACpB,MAAM,EAAE,MAAM,EACd,EAAE,EAAC,GAAG,EACN,SAAS,EAAE,kBAAkB,qBACb,MAAM,EACtB,KAAK,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAC5C,CACH,IACG,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * <VariantSelector> — 商品 variant 选择器
3
+ *
4
+ * 对齐 @shopify/hydrogen <VariantSelector>:
5
+ * - 自动解析 variants 的 selectedOptions 维度(颜色 / 尺寸 等)
6
+ * - 渲染每个维度的选项按钮
7
+ * - 不可购买组合 disabled
8
+ * - URL 同步(可选)
9
+ * - 默认选第一个 availableForSale
10
+ *
11
+ * 设计:组件用 render prop,把每个 option 的选项交给商家渲染。
12
+ *
13
+ * 用法:
14
+ * <VariantSelector
15
+ * product={product}
16
+ * value={selectedVariantId}
17
+ * onChange={setSelectedVariantId}
18
+ * >
19
+ * {({ option, value, onSelect }) => (
20
+ * <div>
21
+ * <h4>{option.name}</h4>
22
+ * {option.values.map((v) => (
23
+ * <button
24
+ * key={v.value}
25
+ * disabled={!v.available}
26
+ * onClick={() => onSelect(v.value)}
27
+ * aria-pressed={v.value === value}
28
+ * >
29
+ * {v.value}
30
+ * </button>
31
+ * ))}
32
+ * </div>
33
+ * )}
34
+ * </VariantSelector>
35
+ */
36
+ import * as React from 'react';
37
+ interface SelectedOption {
38
+ name: string;
39
+ value: string;
40
+ }
41
+ interface ProductVariant {
42
+ id: string;
43
+ availableForSale: boolean;
44
+ selectedOptions?: SelectedOption[];
45
+ }
46
+ interface ProductLike {
47
+ options?: Array<{
48
+ name: string;
49
+ values: string[];
50
+ }>;
51
+ variants: {
52
+ nodes: ProductVariant[];
53
+ };
54
+ }
55
+ export interface VariantOption {
56
+ name: string;
57
+ values: Array<{
58
+ value: string;
59
+ available: boolean;
60
+ }>;
61
+ }
62
+ export interface VariantSelectorRenderProps {
63
+ option: VariantOption;
64
+ /** 当前已选择的 value(在这个 option 维度上) */
65
+ value: string | undefined;
66
+ /** 选择 value,触发 onChange 更新 selectedVariantId */
67
+ onSelect: (value: string) => void;
68
+ }
69
+ export interface VariantSelectorProps {
70
+ product: ProductLike;
71
+ /** 当前选中 variant 的 GID */
72
+ value: string;
73
+ /** variant 切换时调用 */
74
+ onChange?: (variantId: string) => void;
75
+ /** 每个 option 的渲染函数 */
76
+ children: (renderProps: VariantSelectorRenderProps) => React.ReactNode;
77
+ }
78
+ export declare function VariantSelector(props: VariantSelectorProps): import("react/jsx-runtime").JSX.Element;
79
+ export {};
80
+ //# sourceMappingURL=VariantSelector.d.ts.map