@shopbb/helium 0.2.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.
- package/dist/components/AddToCartButton.d.ts +41 -0
- package/dist/components/AddToCartButton.d.ts.map +1 -0
- package/dist/components/AddToCartButton.js +51 -0
- package/dist/components/AddToCartButton.js.map +1 -0
- package/dist/components/CartLineQuantityAdjustButton.d.ts +40 -0
- package/dist/components/CartLineQuantityAdjustButton.d.ts.map +1 -0
- package/dist/components/CartLineQuantityAdjustButton.js +58 -0
- package/dist/components/CartLineQuantityAdjustButton.js.map +1 -0
- package/dist/components/Image.d.ts +39 -0
- package/dist/components/Image.d.ts.map +1 -0
- package/dist/components/Image.js +28 -0
- package/dist/components/Image.js.map +1 -0
- package/dist/components/Money.d.ts +41 -0
- package/dist/components/Money.d.ts.map +1 -0
- package/dist/components/Money.js +48 -0
- package/dist/components/Money.js.map +1 -0
- package/dist/components/ProductPrice.d.ts +28 -0
- package/dist/components/ProductPrice.d.ts.map +1 -0
- package/dist/components/ProductPrice.js +10 -0
- package/dist/components/ProductPrice.js.map +1 -0
- package/dist/components/VariantSelector.d.ts +80 -0
- package/dist/components/VariantSelector.d.ts.map +1 -0
- package/dist/components/VariantSelector.js +87 -0
- package/dist/components/VariantSelector.js.map +1 -0
- package/dist/components/index.d.ts +24 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +18 -0
- package/dist/components/index.js.map +1 -0
- package/package.json +12 -4
- package/src/components/AddToCartButton.tsx +90 -0
- package/src/components/CartLineQuantityAdjustButton.tsx +119 -0
- package/src/components/Image.tsx +93 -0
- package/src/components/Money.tsx +106 -0
- package/src/components/ProductPrice.tsx +61 -0
- package/src/components/VariantSelector.tsx +148 -0
- package/src/components/index.ts +33 -0
|
@@ -0,0 +1,148 @@
|
|
|
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
|
+
|
|
37
|
+
import * as React from 'react';
|
|
38
|
+
|
|
39
|
+
interface SelectedOption {
|
|
40
|
+
name: string;
|
|
41
|
+
value: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface ProductVariant {
|
|
45
|
+
id: string;
|
|
46
|
+
availableForSale: boolean;
|
|
47
|
+
selectedOptions?: SelectedOption[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface ProductLike {
|
|
51
|
+
options?: Array<{ name: string; values: string[] }>;
|
|
52
|
+
variants: { nodes: ProductVariant[] };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface VariantOption {
|
|
56
|
+
name: string;
|
|
57
|
+
values: Array<{ value: string; available: boolean }>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface VariantSelectorRenderProps {
|
|
61
|
+
option: VariantOption;
|
|
62
|
+
/** 当前已选择的 value(在这个 option 维度上) */
|
|
63
|
+
value: string | undefined;
|
|
64
|
+
/** 选择 value,触发 onChange 更新 selectedVariantId */
|
|
65
|
+
onSelect: (value: string) => void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface VariantSelectorProps {
|
|
69
|
+
product: ProductLike;
|
|
70
|
+
/** 当前选中 variant 的 GID */
|
|
71
|
+
value: string;
|
|
72
|
+
/** variant 切换时调用 */
|
|
73
|
+
onChange?: (variantId: string) => void;
|
|
74
|
+
/** 每个 option 的渲染函数 */
|
|
75
|
+
children: (renderProps: VariantSelectorRenderProps) => React.ReactNode;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function VariantSelector(props: VariantSelectorProps) {
|
|
79
|
+
const { product, value, onChange, children } = props;
|
|
80
|
+
|
|
81
|
+
const variants = product.variants.nodes;
|
|
82
|
+
const currentVariant = variants.find((v) => v.id === value) || variants[0];
|
|
83
|
+
|
|
84
|
+
// 推断 options(如果 product.options 没传,从 variants.selectedOptions 提取)
|
|
85
|
+
const options: VariantOption[] = React.useMemo(() => {
|
|
86
|
+
if (product.options && product.options.length > 0) {
|
|
87
|
+
return product.options.map((opt) => ({
|
|
88
|
+
name: opt.name,
|
|
89
|
+
values: opt.values.map((v) => ({
|
|
90
|
+
value: v,
|
|
91
|
+
available: variants.some(
|
|
92
|
+
(variant) =>
|
|
93
|
+
variant.availableForSale &&
|
|
94
|
+
variant.selectedOptions?.some((so) => so.name === opt.name && so.value === v),
|
|
95
|
+
),
|
|
96
|
+
})),
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
// 从 variants.selectedOptions 反推
|
|
100
|
+
const dimMap = new Map<string, Set<string>>();
|
|
101
|
+
for (const v of variants) {
|
|
102
|
+
for (const so of v.selectedOptions ?? []) {
|
|
103
|
+
if (!dimMap.has(so.name)) dimMap.set(so.name, new Set());
|
|
104
|
+
dimMap.get(so.name)!.add(so.value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return Array.from(dimMap.entries()).map(([name, values]) => ({
|
|
108
|
+
name,
|
|
109
|
+
values: Array.from(values).map((v) => ({
|
|
110
|
+
value: v,
|
|
111
|
+
available: variants.some(
|
|
112
|
+
(variant) =>
|
|
113
|
+
variant.availableForSale &&
|
|
114
|
+
variant.selectedOptions?.some((so) => so.name === name && so.value === v),
|
|
115
|
+
),
|
|
116
|
+
})),
|
|
117
|
+
}));
|
|
118
|
+
}, [product, variants]);
|
|
119
|
+
|
|
120
|
+
// 切换某 option 的 value:找一个匹配的 variant
|
|
121
|
+
const handleSelect = (optionName: string, optionValue: string) => {
|
|
122
|
+
const currentSelected = new Map<string, string>(
|
|
123
|
+
(currentVariant?.selectedOptions ?? []).map((so) => [so.name, so.value]),
|
|
124
|
+
);
|
|
125
|
+
currentSelected.set(optionName, optionValue);
|
|
126
|
+
// 找最匹配 + available 的 variant
|
|
127
|
+
const target = variants.find((v) =>
|
|
128
|
+
Array.from(currentSelected.entries()).every(([n, val]) =>
|
|
129
|
+
v.selectedOptions?.some((so) => so.name === n && so.value === val),
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
if (target) onChange?.(target.id);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<>
|
|
137
|
+
{options.map((option) => (
|
|
138
|
+
<React.Fragment key={option.name}>
|
|
139
|
+
{children({
|
|
140
|
+
option,
|
|
141
|
+
value: currentVariant?.selectedOptions?.find((so) => so.name === option.name)?.value,
|
|
142
|
+
onSelect: (val) => handleSelect(option.name, val),
|
|
143
|
+
})}
|
|
144
|
+
</React.Fragment>
|
|
145
|
+
))}
|
|
146
|
+
</>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @shopbb/helium/components — 商家用 React 组件
|
|
3
|
+
*
|
|
4
|
+
* 用法:
|
|
5
|
+
* import { Money, Image, ProductPrice, AddToCartButton } from '@shopbb/helium/components';
|
|
6
|
+
*
|
|
7
|
+
* 设计原则:
|
|
8
|
+
* - 无样式:组件只管行为 + 语义化 DOM,样式商家自己写
|
|
9
|
+
* - data-* 钩子:方便选择器
|
|
10
|
+
* - 对齐 Shopify Hydrogen 同名组件的 API,迁移成本低
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export { Money } from './Money';
|
|
14
|
+
export type { MoneyProps, MoneyData, MoneyMeasurement } from './Money';
|
|
15
|
+
|
|
16
|
+
export { Image } from './Image';
|
|
17
|
+
export type { ImageProps, ImageData } from './Image';
|
|
18
|
+
|
|
19
|
+
export { ProductPrice } from './ProductPrice';
|
|
20
|
+
export type { ProductPriceProps } from './ProductPrice';
|
|
21
|
+
|
|
22
|
+
export { AddToCartButton } from './AddToCartButton';
|
|
23
|
+
export type { AddToCartButtonProps } from './AddToCartButton';
|
|
24
|
+
|
|
25
|
+
export { CartLineQuantityAdjustButton } from './CartLineQuantityAdjustButton';
|
|
26
|
+
export type { CartLineQuantityAdjustButtonProps } from './CartLineQuantityAdjustButton';
|
|
27
|
+
|
|
28
|
+
export { VariantSelector } from './VariantSelector';
|
|
29
|
+
export type {
|
|
30
|
+
VariantSelectorProps,
|
|
31
|
+
VariantSelectorRenderProps,
|
|
32
|
+
VariantOption,
|
|
33
|
+
} from './VariantSelector';
|