@shopbb/helium 0.2.0 → 0.3.0-alpha.2

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 (56) hide show
  1. package/dist/components/AddToCartButton.d.ts +41 -0
  2. package/dist/components/AddToCartButton.d.ts.map +1 -0
  3. package/dist/components/AddToCartButton.js +63 -0
  4. package/dist/components/AddToCartButton.js.map +1 -0
  5. package/dist/components/AnalyticsProvider.d.ts +106 -0
  6. package/dist/components/AnalyticsProvider.d.ts.map +1 -0
  7. package/dist/components/AnalyticsProvider.js +135 -0
  8. package/dist/components/AnalyticsProvider.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/CartProvider.d.ts +111 -0
  14. package/dist/components/CartProvider.d.ts.map +1 -0
  15. package/dist/components/CartProvider.js +263 -0
  16. package/dist/components/CartProvider.js.map +1 -0
  17. package/dist/components/Image.d.ts +39 -0
  18. package/dist/components/Image.d.ts.map +1 -0
  19. package/dist/components/Image.js +28 -0
  20. package/dist/components/Image.js.map +1 -0
  21. package/dist/components/Money.d.ts +41 -0
  22. package/dist/components/Money.d.ts.map +1 -0
  23. package/dist/components/Money.js +53 -0
  24. package/dist/components/Money.js.map +1 -0
  25. package/dist/components/ProductOptionsProvider.d.ts +70 -0
  26. package/dist/components/ProductOptionsProvider.d.ts.map +1 -0
  27. package/dist/components/ProductOptionsProvider.js +93 -0
  28. package/dist/components/ProductOptionsProvider.js.map +1 -0
  29. package/dist/components/ProductPrice.d.ts +28 -0
  30. package/dist/components/ProductPrice.d.ts.map +1 -0
  31. package/dist/components/ProductPrice.js +10 -0
  32. package/dist/components/ProductPrice.js.map +1 -0
  33. package/dist/components/ShopProvider.d.ts +49 -0
  34. package/dist/components/ShopProvider.d.ts.map +1 -0
  35. package/dist/components/ShopProvider.js +62 -0
  36. package/dist/components/ShopProvider.js.map +1 -0
  37. package/dist/components/VariantSelector.d.ts +80 -0
  38. package/dist/components/VariantSelector.d.ts.map +1 -0
  39. package/dist/components/VariantSelector.js +87 -0
  40. package/dist/components/VariantSelector.js.map +1 -0
  41. package/dist/components/index.d.ts +41 -0
  42. package/dist/components/index.d.ts.map +1 -0
  43. package/dist/components/index.js +33 -0
  44. package/dist/components/index.js.map +1 -0
  45. package/package.json +12 -4
  46. package/src/components/AddToCartButton.tsx +101 -0
  47. package/src/components/AnalyticsProvider.tsx +175 -0
  48. package/src/components/CartLineQuantityAdjustButton.tsx +119 -0
  49. package/src/components/CartProvider.tsx +378 -0
  50. package/src/components/Image.tsx +93 -0
  51. package/src/components/Money.tsx +112 -0
  52. package/src/components/ProductOptionsProvider.tsx +149 -0
  53. package/src/components/ProductPrice.tsx +61 -0
  54. package/src/components/ShopProvider.tsx +86 -0
  55. package/src/components/VariantSelector.tsx +148 -0
  56. package/src/components/index.ts +71 -0
@@ -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,49 @@
1
+ /**
2
+ * <ShopProvider> + useShop()
3
+ *
4
+ * 全局店铺上下文 — 注入 storefront token / apiUrl / 货币 / locale / shop 元信息。
5
+ * 子组件用 useShop() 直接拿,不用 prop-drill。
6
+ *
7
+ * 用法:
8
+ * <ShopProvider
9
+ * storeId="store_xxx"
10
+ * storefrontAccessToken="shpat_pub_..."
11
+ * apiUrl="https://api.example.com/api/2026-04/graphql.json"
12
+ * shopName="Shopflare"
13
+ * currencyCode="CNY"
14
+ * locale="zh-CN"
15
+ * >
16
+ * <App />
17
+ * </ShopProvider>
18
+ *
19
+ * // 任意子组件:
20
+ * const { storeId, storefrontAccessToken } = useShop();
21
+ */
22
+ import * as React from 'react';
23
+ export interface ShopContextValue {
24
+ storeId: string;
25
+ storefrontAccessToken: string;
26
+ apiUrl: string;
27
+ shopName: string;
28
+ currencyCode: string;
29
+ locale: string;
30
+ /** 额外自定义字段,用户可塞任意业务 meta */
31
+ meta?: Record<string, any>;
32
+ }
33
+ export interface ShopProviderProps extends Partial<ShopContextValue> {
34
+ storeId: string;
35
+ storefrontAccessToken: string;
36
+ apiUrl: string;
37
+ children: React.ReactNode;
38
+ }
39
+ export declare function ShopProvider(props: ShopProviderProps): import("react/jsx-runtime").JSX.Element;
40
+ /**
41
+ * 拿当前 shop context。Provider 外调用会 throw。
42
+ */
43
+ export declare function useShop(): ShopContextValue;
44
+ /**
45
+ * 非 throw 版本:Provider 外返回 null。
46
+ * 给组件做"可选 fallback"用,比如 <Money> 在没 ShopProvider 时也要能跑。
47
+ */
48
+ export declare function useShopOptional(): ShopContextValue | null;
49
+ //# sourceMappingURL=ShopProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShopProvider.d.ts","sourceRoot":"","sources":["../../src/components/ShopProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC5B;AAID,MAAM,WAAW,iBAAkB,SAAQ,OAAO,CAAC,gBAAgB,CAAC;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,2CAsBpD;AAED;;GAEG;AACH,wBAAgB,OAAO,IAAI,gBAAgB,CAM1C;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,gBAAgB,GAAG,IAAI,CAEzD"}
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * <ShopProvider> + useShop()
4
+ *
5
+ * 全局店铺上下文 — 注入 storefront token / apiUrl / 货币 / locale / shop 元信息。
6
+ * 子组件用 useShop() 直接拿,不用 prop-drill。
7
+ *
8
+ * 用法:
9
+ * <ShopProvider
10
+ * storeId="store_xxx"
11
+ * storefrontAccessToken="shpat_pub_..."
12
+ * apiUrl="https://api.example.com/api/2026-04/graphql.json"
13
+ * shopName="Shopflare"
14
+ * currencyCode="CNY"
15
+ * locale="zh-CN"
16
+ * >
17
+ * <App />
18
+ * </ShopProvider>
19
+ *
20
+ * // 任意子组件:
21
+ * const { storeId, storefrontAccessToken } = useShop();
22
+ */
23
+ import * as React from 'react';
24
+ const ShopContext = React.createContext(null);
25
+ export function ShopProvider(props) {
26
+ const value = React.useMemo(() => ({
27
+ storeId: props.storeId,
28
+ storefrontAccessToken: props.storefrontAccessToken,
29
+ apiUrl: props.apiUrl,
30
+ shopName: props.shopName ?? '',
31
+ currencyCode: props.currencyCode ?? 'USD',
32
+ locale: props.locale ?? 'en-US',
33
+ meta: props.meta,
34
+ }), [
35
+ props.storeId,
36
+ props.storefrontAccessToken,
37
+ props.apiUrl,
38
+ props.shopName,
39
+ props.currencyCode,
40
+ props.locale,
41
+ props.meta,
42
+ ]);
43
+ return _jsx(ShopContext.Provider, { value: value, children: props.children });
44
+ }
45
+ /**
46
+ * 拿当前 shop context。Provider 外调用会 throw。
47
+ */
48
+ export function useShop() {
49
+ const v = React.useContext(ShopContext);
50
+ if (!v) {
51
+ throw new Error('useShop must be used inside <ShopProvider>');
52
+ }
53
+ return v;
54
+ }
55
+ /**
56
+ * 非 throw 版本:Provider 外返回 null。
57
+ * 给组件做"可选 fallback"用,比如 <Money> 在没 ShopProvider 时也要能跑。
58
+ */
59
+ export function useShopOptional() {
60
+ return React.useContext(ShopContext);
61
+ }
62
+ //# sourceMappingURL=ShopProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ShopProvider.js","sourceRoot":"","sources":["../../src/components/ShopProvider.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAa/B,MAAM,WAAW,GAAG,KAAK,CAAC,aAAa,CAA0B,IAAI,CAAC,CAAC;AASvE,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CACzB,GAAG,EAAE,CAAC,CAAC;QACL,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,qBAAqB,EAAE,KAAK,CAAC,qBAAqB;QAClD,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,EAAE;QAC9B,YAAY,EAAE,KAAK,CAAC,YAAY,IAAI,KAAK;QACzC,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,OAAO;QAC/B,IAAI,EAAE,KAAK,CAAC,IAAI;KACjB,CAAC,EACF;QACE,KAAK,CAAC,OAAO;QACb,KAAK,CAAC,qBAAqB;QAC3B,KAAK,CAAC,MAAM;QACZ,KAAK,CAAC,QAAQ;QACd,KAAK,CAAC,YAAY;QAClB,KAAK,CAAC,MAAM;QACZ,KAAK,CAAC,IAAI;KACX,CACF,CAAC;IACF,OAAO,KAAC,WAAW,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,KAAK,CAAC,QAAQ,GAAwB,CAAC;AACrF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO;IACrB,MAAM,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;IACxC,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,OAAO,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AACvC,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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VariantSelector.d.ts","sourceRoot":"","sources":["../../src/components/VariantSelector.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;CACpC;AAED,UAAU,WAAW;IACnB,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACpD,QAAQ,EAAE;QAAE,KAAK,EAAE,cAAc,EAAE,CAAA;KAAE,CAAC;CACvC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,aAAa,CAAC;IACtB,mCAAmC;IACnC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,gDAAgD;IAChD,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,WAAW,CAAC;IACrB,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB;IACpB,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,sBAAsB;IACtB,QAAQ,EAAE,CAAC,WAAW,EAAE,0BAA0B,KAAK,KAAK,CAAC,SAAS,CAAC;CACxE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,2CAsE1D"}
@@ -0,0 +1,87 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * <VariantSelector> — 商品 variant 选择器
4
+ *
5
+ * 对齐 @shopify/hydrogen <VariantSelector>:
6
+ * - 自动解析 variants 的 selectedOptions 维度(颜色 / 尺寸 等)
7
+ * - 渲染每个维度的选项按钮
8
+ * - 不可购买组合 disabled
9
+ * - URL 同步(可选)
10
+ * - 默认选第一个 availableForSale
11
+ *
12
+ * 设计:组件用 render prop,把每个 option 的选项交给商家渲染。
13
+ *
14
+ * 用法:
15
+ * <VariantSelector
16
+ * product={product}
17
+ * value={selectedVariantId}
18
+ * onChange={setSelectedVariantId}
19
+ * >
20
+ * {({ option, value, onSelect }) => (
21
+ * <div>
22
+ * <h4>{option.name}</h4>
23
+ * {option.values.map((v) => (
24
+ * <button
25
+ * key={v.value}
26
+ * disabled={!v.available}
27
+ * onClick={() => onSelect(v.value)}
28
+ * aria-pressed={v.value === value}
29
+ * >
30
+ * {v.value}
31
+ * </button>
32
+ * ))}
33
+ * </div>
34
+ * )}
35
+ * </VariantSelector>
36
+ */
37
+ import * as React from 'react';
38
+ export function VariantSelector(props) {
39
+ const { product, value, onChange, children } = props;
40
+ const variants = product.variants.nodes;
41
+ const currentVariant = variants.find((v) => v.id === value) || variants[0];
42
+ // 推断 options(如果 product.options 没传,从 variants.selectedOptions 提取)
43
+ const options = React.useMemo(() => {
44
+ if (product.options && product.options.length > 0) {
45
+ return product.options.map((opt) => ({
46
+ name: opt.name,
47
+ values: opt.values.map((v) => ({
48
+ value: v,
49
+ available: variants.some((variant) => variant.availableForSale &&
50
+ variant.selectedOptions?.some((so) => so.name === opt.name && so.value === v)),
51
+ })),
52
+ }));
53
+ }
54
+ // 从 variants.selectedOptions 反推
55
+ const dimMap = new Map();
56
+ for (const v of variants) {
57
+ for (const so of v.selectedOptions ?? []) {
58
+ if (!dimMap.has(so.name))
59
+ dimMap.set(so.name, new Set());
60
+ dimMap.get(so.name).add(so.value);
61
+ }
62
+ }
63
+ return Array.from(dimMap.entries()).map(([name, values]) => ({
64
+ name,
65
+ values: Array.from(values).map((v) => ({
66
+ value: v,
67
+ available: variants.some((variant) => variant.availableForSale &&
68
+ variant.selectedOptions?.some((so) => so.name === name && so.value === v)),
69
+ })),
70
+ }));
71
+ }, [product, variants]);
72
+ // 切换某 option 的 value:找一个匹配的 variant
73
+ const handleSelect = (optionName, optionValue) => {
74
+ const currentSelected = new Map((currentVariant?.selectedOptions ?? []).map((so) => [so.name, so.value]));
75
+ currentSelected.set(optionName, optionValue);
76
+ // 找最匹配 + available 的 variant
77
+ const target = variants.find((v) => Array.from(currentSelected.entries()).every(([n, val]) => v.selectedOptions?.some((so) => so.name === n && so.value === val)));
78
+ if (target)
79
+ onChange?.(target.id);
80
+ };
81
+ return (_jsx(_Fragment, { children: options.map((option) => (_jsx(React.Fragment, { children: children({
82
+ option,
83
+ value: currentVariant?.selectedOptions?.find((so) => so.name === option.name)?.value,
84
+ onSelect: (val) => handleSelect(option.name, val),
85
+ }) }, option.name))) }));
86
+ }
87
+ //# sourceMappingURL=VariantSelector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VariantSelector.js","sourceRoot":"","sources":["../../src/components/VariantSelector.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @shopbb/helium/components — 商家用 React 组件
3
+ *
4
+ * 用法:
5
+ * import {
6
+ * ShopProvider, CartProvider, AnalyticsProvider,
7
+ * useShop, useCart, useAnalytics,
8
+ * Money, Image, ProductPrice,
9
+ * AddToCartButton, CartLineQuantityAdjustButton,
10
+ * VariantSelector, ProductOptionsProvider, useProductOptions,
11
+ * Analytics,
12
+ * } from '@shopbb/helium/components';
13
+ *
14
+ * 设计原则:
15
+ * - 无样式:组件只管行为 + 语义化 DOM,样式商家自己写
16
+ * - data-* 钩子:方便选择器
17
+ * - 对齐 Shopify Hydrogen 同名组件的 API,迁移成本低
18
+ * - Provider 链:<ShopProvider> > <CartProvider> > <AnalyticsProvider> > App
19
+ * 组件优先从 Provider 拿;Provider 缺省时回退到 props 或 noop
20
+ */
21
+ export { ShopProvider, useShop, useShopOptional } from './ShopProvider';
22
+ export type { ShopContextValue, ShopProviderProps } from './ShopProvider';
23
+ export { CartProvider, useCart, useCartOptional } from './CartProvider';
24
+ export type { Cart, CartLine, CartLineMerchandise, CartStatus, CartUserError, CartContextValue, CartProviderProps, } from './CartProvider';
25
+ export { ProductOptionsProvider, useProductOptions, } from './ProductOptionsProvider';
26
+ export type { ProductOptionsContextValue, ProductOptionsProviderProps, ProductOptionItem, } from './ProductOptionsProvider';
27
+ export { AnalyticsProvider, useAnalytics, Analytics } from './AnalyticsProvider';
28
+ export type { AnalyticsEvent, AnalyticsContextValue, AnalyticsProviderProps } from './AnalyticsProvider';
29
+ export { Money } from './Money';
30
+ export type { MoneyProps, MoneyData, MoneyMeasurement } from './Money';
31
+ export { Image } from './Image';
32
+ export type { ImageProps, ImageData } from './Image';
33
+ export { ProductPrice } from './ProductPrice';
34
+ export type { ProductPriceProps } from './ProductPrice';
35
+ export { AddToCartButton } from './AddToCartButton';
36
+ export type { AddToCartButtonProps } from './AddToCartButton';
37
+ export { CartLineQuantityAdjustButton } from './CartLineQuantityAdjustButton';
38
+ export type { CartLineQuantityAdjustButtonProps } from './CartLineQuantityAdjustButton';
39
+ export { VariantSelector } from './VariantSelector';
40
+ export type { VariantSelectorProps, VariantSelectorRenderProps, VariantOption, } from './VariantSelector';
41
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACxE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAE1E,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACxE,YAAY,EACV,IAAI,EACJ,QAAQ,EACR,mBAAmB,EACnB,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,gBAAgB,CAAC;AAExB,OAAO,EACL,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,iBAAiB,GAClB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACjF,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAGzG,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEvE,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAErD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAC9E,YAAY,EAAE,iCAAiC,EAAE,MAAM,gCAAgC,CAAC;AAExF,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,YAAY,EACV,oBAAoB,EACpB,0BAA0B,EAC1B,aAAa,GACd,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @shopbb/helium/components — 商家用 React 组件
3
+ *
4
+ * 用法:
5
+ * import {
6
+ * ShopProvider, CartProvider, AnalyticsProvider,
7
+ * useShop, useCart, useAnalytics,
8
+ * Money, Image, ProductPrice,
9
+ * AddToCartButton, CartLineQuantityAdjustButton,
10
+ * VariantSelector, ProductOptionsProvider, useProductOptions,
11
+ * Analytics,
12
+ * } from '@shopbb/helium/components';
13
+ *
14
+ * 设计原则:
15
+ * - 无样式:组件只管行为 + 语义化 DOM,样式商家自己写
16
+ * - data-* 钩子:方便选择器
17
+ * - 对齐 Shopify Hydrogen 同名组件的 API,迁移成本低
18
+ * - Provider 链:<ShopProvider> > <CartProvider> > <AnalyticsProvider> > App
19
+ * 组件优先从 Provider 拿;Provider 缺省时回退到 props 或 noop
20
+ */
21
+ // Providers + hooks
22
+ export { ShopProvider, useShop, useShopOptional } from './ShopProvider';
23
+ export { CartProvider, useCart, useCartOptional } from './CartProvider';
24
+ export { ProductOptionsProvider, useProductOptions, } from './ProductOptionsProvider';
25
+ export { AnalyticsProvider, useAnalytics, Analytics } from './AnalyticsProvider';
26
+ // 展示型组件
27
+ export { Money } from './Money';
28
+ export { Image } from './Image';
29
+ export { ProductPrice } from './ProductPrice';
30
+ export { AddToCartButton } from './AddToCartButton';
31
+ export { CartLineQuantityAdjustButton } from './CartLineQuantityAdjustButton';
32
+ export { VariantSelector } from './VariantSelector';
33
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,oBAAoB;AACpB,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGxE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAWxE,OAAO,EACL,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,0BAA0B,CAAC;AAOlC,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAGjF,QAAQ;AACR,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGpD,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAG9E,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shopbb/helium",
3
- "version": "0.2.0",
4
- "description": "shopbb storefront framework — GraphQL client, cart handler, cache helpers, and React SSR for Cloudflare Workers",
3
+ "version": "0.3.0-alpha.2",
4
+ "description": "shopbb storefront framework — components, React SSR, GraphQL client, cart handler, cache for Cloudflare Workers",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -20,6 +20,10 @@
20
20
  "./client": {
21
21
  "types": "./dist/client.d.ts",
22
22
  "import": "./dist/client.js"
23
+ },
24
+ "./components": {
25
+ "types": "./dist/components/index.d.ts",
26
+ "import": "./dist/components/index.js"
23
27
  }
24
28
  },
25
29
  "files": [
@@ -48,8 +52,12 @@
48
52
  "react-dom": ">=18"
49
53
  },
50
54
  "peerDependenciesMeta": {
51
- "react": { "optional": true },
52
- "react-dom": { "optional": true }
55
+ "react": {
56
+ "optional": true
57
+ },
58
+ "react-dom": {
59
+ "optional": true
60
+ }
53
61
  },
54
62
  "devDependencies": {
55
63
  "@cloudflare/workers-types": "^4.20240117.0",
@@ -0,0 +1,101 @@
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
+
24
+ import * as React from 'react';
25
+ import { useCartOptional } from './CartProvider';
26
+ import { useAnalytics } from './AnalyticsProvider';
27
+
28
+ export interface AddToCartButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onClick' | 'onError'> {
29
+ /** 必传:variant 的 GID(gid://shopbb/ProductVariant/...) */
30
+ variantId: string;
31
+ /** 数量,默认 1 */
32
+ quantity?: number;
33
+ /** 实际加购回调。Phase 1 必传;Phase 2 后可从 Provider 自动获取 */
34
+ onAdd?: (variantId: string, quantity: number) => Promise<void>;
35
+ /** 加购成功回调(如导航到 /cart) */
36
+ onAdded?: () => void;
37
+ /** 加购失败回调 */
38
+ onError?: (err: Error) => void;
39
+ /** 加购中显示的文本,默认 "加入中..." */
40
+ loadingText?: React.ReactNode;
41
+ /** 不可用时显示的文本,默认 "缺货" */
42
+ unavailableText?: React.ReactNode;
43
+ }
44
+
45
+ export function AddToCartButton(props: AddToCartButtonProps) {
46
+ const {
47
+ variantId,
48
+ quantity = 1,
49
+ onAdd,
50
+ onAdded,
51
+ onError,
52
+ loadingText = '加入中...',
53
+ unavailableText = '缺货',
54
+ children = '加入购物车',
55
+ disabled: disabledProp,
56
+ ...rest
57
+ } = props;
58
+
59
+ const [adding, setAdding] = React.useState(false);
60
+ const cart = useCartOptional();
61
+ const analytics = useAnalytics();
62
+
63
+ const handleClick = async (e: React.MouseEvent<HTMLButtonElement>) => {
64
+ e.preventDefault();
65
+ if (adding || disabledProp) return;
66
+
67
+ setAdding(true);
68
+ try {
69
+ if (onAdd) {
70
+ // 商家显式传了 onAdd 优先用(Phase 1 兼容路径)
71
+ await onAdd(variantId, quantity);
72
+ } else if (cart) {
73
+ // 自动从 <CartProvider> 取
74
+ await cart.linesAdd([{ merchandiseId: variantId, quantity }]);
75
+ } else {
76
+ console.warn('[AddToCartButton] no onAdd and no <CartProvider> — button does nothing');
77
+ return;
78
+ }
79
+ analytics.emit('add_to_cart', { variantId, quantity });
80
+ onAdded?.();
81
+ } catch (err: any) {
82
+ console.error('[AddToCartButton] add failed:', err);
83
+ onError?.(err instanceof Error ? err : new Error(String(err)));
84
+ } finally {
85
+ setAdding(false);
86
+ }
87
+ };
88
+
89
+ return (
90
+ <button
91
+ type="button"
92
+ {...rest}
93
+ data-add-to-cart
94
+ data-loading={adding ? '' : undefined}
95
+ disabled={disabledProp || adding}
96
+ onClick={handleClick}
97
+ >
98
+ {adding ? loadingText : disabledProp ? unavailableText : children}
99
+ </button>
100
+ );
101
+ }