@snowcone-app/ui 0.8.2 → 0.9.0
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 +20 -0
- package/dist/index.cjs +101 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -17
- package/dist/index.d.ts +70 -17
- package/dist/index.js +126 -58
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/composed/AddToCart.tsx +51 -31
- package/src/composed/PlacementTabs.tsx +117 -19
- package/src/composed/__tests__/AddToCart.async.test.tsx +104 -0
- package/src/composed/__tests__/PlacementTabs.linked.test.tsx +144 -0
- package/src/patterns/RealtimeProvider.tsx +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#2936](https://github.com/snowcone-app/snowcone-monorepo/pull/2936) [`3c37e27`](https://github.com/snowcone-app/snowcone-monorepo/commit/3c37e27da40c8865b4c4bd71ec9454ec7e64bc4c) Thanks [@kevinsproles](https://github.com/kevinsproles)! - `AddToCart` waits for your add and never reports a failure as success. An async `onClick` is awaited: the button shows
|
|
8
|
+
"Added!" only after it resolves, and a rejection (or throw) shows `errorText` (new prop, default
|
|
9
|
+
"Couldn't add — try again"), announces the error's message in a `role="alert"`, calls `onError`, and leaves the button
|
|
10
|
+
enabled to retry. Previously `onClick` was called without awaiting after a fixed simulated delay, so a failed add still
|
|
11
|
+
said "Added!". The button exposes `data-add-to-cart="idle | adding | added | error"` (type `AddToCartStatus`) for tests.
|
|
12
|
+
`simulateDelay` now applies only when there is no `onClick` (demos).
|
|
13
|
+
|
|
14
|
+
## 0.8.3
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- [#3131](https://github.com/snowcone-app/snowcone-monorepo/pull/3131) [`84f6ac3`](https://github.com/snowcone-app/snowcone-monorepo/commit/84f6ac38e54c57c483ff49c18548cd9ba832d30e) Thanks [@kevinsproles](https://github.com/kevinsproles)! - Realtime `spreadCrops` pieces can carry `part: { index, count }`: a placement cut into strips that sit on different spread artboards is assembled server-side once every strip has arrived.
|
|
19
|
+
|
|
20
|
+
- Updated dependencies [[`84f6ac3`](https://github.com/snowcone-app/snowcone-monorepo/commit/84f6ac38e54c57c483ff49c18548cd9ba832d30e)]:
|
|
21
|
+
- @snowcone-app/sdk@0.24.2
|
|
22
|
+
|
|
3
23
|
## 0.8.2
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -342,6 +342,7 @@ __export(src_exports, {
|
|
|
342
342
|
isWideMonitor: () => isWideMonitor,
|
|
343
343
|
kbdVariants: () => kbdVariants,
|
|
344
344
|
linkVariants: () => linkVariants,
|
|
345
|
+
linkedPlacementsLabel: () => linkedPlacementsLabel,
|
|
345
346
|
listProducts: () => import_sdk11.listProducts,
|
|
346
347
|
normalizeHex: () => normalizeHex,
|
|
347
348
|
observe: () => observe,
|
|
@@ -4920,6 +4921,7 @@ function AddToCart({
|
|
|
4920
4921
|
text = "Add to Bag",
|
|
4921
4922
|
loadingText = "Adding...",
|
|
4922
4923
|
successText = "Added!",
|
|
4924
|
+
errorText = "Couldn't add \u2014 try again",
|
|
4923
4925
|
disabled = false,
|
|
4924
4926
|
className = "",
|
|
4925
4927
|
quantity = 1,
|
|
@@ -4929,8 +4931,10 @@ function AddToCart({
|
|
|
4929
4931
|
}) {
|
|
4930
4932
|
const context = useProduct();
|
|
4931
4933
|
const shopContext = useShopOptional();
|
|
4932
|
-
const [
|
|
4933
|
-
const [
|
|
4934
|
+
const [status, setStatus] = (0, import_react15.useState)("idle");
|
|
4935
|
+
const [errorMessage, setErrorMessage] = (0, import_react15.useState)(null);
|
|
4936
|
+
const loading = status === "adding";
|
|
4937
|
+
const success = status === "added";
|
|
4934
4938
|
(0, import_react15.useEffect)(() => {
|
|
4935
4939
|
checkProviders(
|
|
4936
4940
|
"AddToCart",
|
|
@@ -4953,7 +4957,8 @@ function AddToCart({
|
|
|
4953
4957
|
const handleClick = async () => {
|
|
4954
4958
|
if (isDisabled || !product) return;
|
|
4955
4959
|
try {
|
|
4956
|
-
|
|
4960
|
+
setStatus("adding");
|
|
4961
|
+
setErrorMessage(null);
|
|
4957
4962
|
const cartDetail = (0, import_sdk7.createCartDetail)({
|
|
4958
4963
|
productId: product.id,
|
|
4959
4964
|
quantity,
|
|
@@ -4964,20 +4969,23 @@ function AddToCart({
|
|
|
4964
4969
|
if (!cartDetail) {
|
|
4965
4970
|
throw new Error("Invalid selection - unable to create cart detail");
|
|
4966
4971
|
}
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4972
|
+
if (onClick) {
|
|
4973
|
+
await onClick(cartDetail);
|
|
4974
|
+
} else {
|
|
4975
|
+
await (0, import_sdk7.simulateCartOperation)(simulateDelay);
|
|
4976
|
+
}
|
|
4977
|
+
setStatus("added");
|
|
4970
4978
|
setTimeout(() => {
|
|
4971
|
-
|
|
4979
|
+
setStatus((s) => s === "added" ? "idle" : s);
|
|
4972
4980
|
}, 2e3);
|
|
4973
4981
|
} catch (error) {
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4982
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
4983
|
+
setStatus("error");
|
|
4984
|
+
setErrorMessage(err.message);
|
|
4985
|
+
onError?.(err);
|
|
4978
4986
|
}
|
|
4979
4987
|
};
|
|
4980
|
-
const buttonText = loading ? loadingText : success ? successText : text;
|
|
4988
|
+
const buttonText = loading ? loadingText : success ? successText : status === "error" ? errorText : text;
|
|
4981
4989
|
const defaultStyles = "w-full bg-primary text-primary-foreground hover:bg-primary/90 px-6 py-3 rounded-button font-medium transition-colors";
|
|
4982
4990
|
const buttonClasses = `
|
|
4983
4991
|
${defaultStyles}
|
|
@@ -4988,6 +4996,7 @@ function AddToCart({
|
|
|
4988
4996
|
const getAriaLabel = () => {
|
|
4989
4997
|
if (loading) return loadingText;
|
|
4990
4998
|
if (success) return successText;
|
|
4999
|
+
if (status === "error") return `${errorText} - ${product?.name || "product"}`;
|
|
4991
5000
|
if (!product) return text;
|
|
4992
5001
|
return `${text} - ${product.name || "product"}`;
|
|
4993
5002
|
};
|
|
@@ -5001,9 +5010,11 @@ function AddToCart({
|
|
|
5001
5010
|
onClick: handleClick,
|
|
5002
5011
|
"aria-label": getAriaLabel(),
|
|
5003
5012
|
"aria-busy": loading,
|
|
5013
|
+
"data-add-to-cart": status,
|
|
5004
5014
|
children: buttonText
|
|
5005
5015
|
}
|
|
5006
|
-
)
|
|
5016
|
+
),
|
|
5017
|
+
status === "error" && errorMessage && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("p", { role: "alert", className: "mt-2 text-sm text-destructive", children: errorMessage })
|
|
5007
5018
|
] });
|
|
5008
5019
|
}
|
|
5009
5020
|
|
|
@@ -5750,6 +5761,11 @@ function PlacementClipShapeSelector() {
|
|
|
5750
5761
|
// src/composed/PlacementTabs.tsx
|
|
5751
5762
|
var import_react21 = require("react");
|
|
5752
5763
|
var import_jsx_runtime50 = require("react/jsx-runtime");
|
|
5764
|
+
function linkedPlacementsLabel(count) {
|
|
5765
|
+
return count === 2 ? "Both sides" : `All ${count} areas`;
|
|
5766
|
+
}
|
|
5767
|
+
var ACTION_CLASS = "transition-colors duration-200 cursor-pointer text-muted-foreground hover:text-foreground underline underline-offset-4 decoration-muted-foreground/40 hover:decoration-foreground";
|
|
5768
|
+
var PILL_CLASS = "inline-flex items-center rounded-full bg-foreground/[0.08] px-3 py-1 font-medium text-foreground";
|
|
5753
5769
|
function PlacementTabs({
|
|
5754
5770
|
placements,
|
|
5755
5771
|
selectedPlacement,
|
|
@@ -5757,7 +5773,13 @@ function PlacementTabs({
|
|
|
5757
5773
|
isDesktop = false,
|
|
5758
5774
|
toolbarPlaceholderId = "toolbar-placeholder",
|
|
5759
5775
|
toolbarSize = 120,
|
|
5760
|
-
className = ""
|
|
5776
|
+
className = "",
|
|
5777
|
+
linked,
|
|
5778
|
+
onSplitAll,
|
|
5779
|
+
onLinkAll,
|
|
5780
|
+
linkedLabel,
|
|
5781
|
+
splitAllLabel = "Customize separately",
|
|
5782
|
+
linkAllLabel = "Use one design for all"
|
|
5761
5783
|
}) {
|
|
5762
5784
|
const containerRef = (0, import_react21.useRef)(null);
|
|
5763
5785
|
const [lineHeight, setLineHeight] = (0, import_react21.useState)(24);
|
|
@@ -5774,7 +5796,7 @@ function PlacementTabs({
|
|
|
5774
5796
|
if (isDesktop) {
|
|
5775
5797
|
return null;
|
|
5776
5798
|
}
|
|
5777
|
-
return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: `flex justify-end w-full ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
5799
|
+
return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)("div", { className: `flex justify-end w-full ${className}`, "data-placement-tabs": true, children: /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
5778
5800
|
"span",
|
|
5779
5801
|
{
|
|
5780
5802
|
id: toolbarPlaceholderId,
|
|
@@ -5783,16 +5805,68 @@ function PlacementTabs({
|
|
|
5783
5805
|
}
|
|
5784
5806
|
) });
|
|
5785
5807
|
}
|
|
5808
|
+
const dataLinked = linked === void 0 ? void 0 : linked ? "true" : "false";
|
|
5809
|
+
const labels = placements.map((p) => p.label);
|
|
5810
|
+
const renderItems = (tabClass) => {
|
|
5811
|
+
if (linked === true) {
|
|
5812
|
+
return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_jsx_runtime50.Fragment, { children: [
|
|
5813
|
+
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
5814
|
+
"span",
|
|
5815
|
+
{
|
|
5816
|
+
className: `${tabClass} ${PILL_CLASS}`,
|
|
5817
|
+
"data-placement-pill": true,
|
|
5818
|
+
title: labels.join(" \xB7 "),
|
|
5819
|
+
"aria-label": `One design on ${labels.join(", ")}`,
|
|
5820
|
+
children: linkedLabel ?? linkedPlacementsLabel(placements.length)
|
|
5821
|
+
}
|
|
5822
|
+
),
|
|
5823
|
+
onSplitAll && /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
5824
|
+
"button",
|
|
5825
|
+
{
|
|
5826
|
+
type: "button",
|
|
5827
|
+
onClick: onSplitAll,
|
|
5828
|
+
className: `${tabClass} ${ACTION_CLASS}`,
|
|
5829
|
+
"data-action": "split-all",
|
|
5830
|
+
children: splitAllLabel
|
|
5831
|
+
}
|
|
5832
|
+
)
|
|
5833
|
+
] });
|
|
5834
|
+
}
|
|
5835
|
+
return /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)(import_jsx_runtime50.Fragment, { children: [
|
|
5836
|
+
placements.map((p) => /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
5837
|
+
"button",
|
|
5838
|
+
{
|
|
5839
|
+
type: "button",
|
|
5840
|
+
onClick: () => onPlacementChange(p.label),
|
|
5841
|
+
"data-placement-tab": p.label,
|
|
5842
|
+
"aria-pressed": selectedPlacement === p.label,
|
|
5843
|
+
className: `${tabClass} transition-all duration-200 cursor-pointer border-b-2 ${selectedPlacement === p.label ? "text-foreground border-primary" : "text-foreground/70 hover:text-foreground border-muted-foreground/20 hover:border-muted-foreground/40"}`,
|
|
5844
|
+
children: p.label
|
|
5845
|
+
},
|
|
5846
|
+
p.label
|
|
5847
|
+
)),
|
|
5848
|
+
linked === false && onLinkAll && /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
5849
|
+
"button",
|
|
5850
|
+
{
|
|
5851
|
+
type: "button",
|
|
5852
|
+
onClick: onLinkAll,
|
|
5853
|
+
className: `${tabClass} ${ACTION_CLASS}`,
|
|
5854
|
+
"data-action": "link-all",
|
|
5855
|
+
children: linkAllLabel
|
|
5856
|
+
}
|
|
5857
|
+
)
|
|
5858
|
+
] });
|
|
5859
|
+
};
|
|
5786
5860
|
if (isDesktop) {
|
|
5787
|
-
return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
5788
|
-
"
|
|
5861
|
+
return /* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
5862
|
+
"div",
|
|
5789
5863
|
{
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
)
|
|
5864
|
+
className: `flex flex-wrap items-center gap-x-3 gap-y-1 text-sm leading-relaxed w-full ${className}`,
|
|
5865
|
+
"data-placement-tabs": true,
|
|
5866
|
+
"data-linked": dataLinked,
|
|
5867
|
+
children: renderItems("")
|
|
5868
|
+
}
|
|
5869
|
+
);
|
|
5796
5870
|
}
|
|
5797
5871
|
const linesForToolbar = Math.ceil(toolbarSize / lineHeight);
|
|
5798
5872
|
const shapeInsetPx = linesForToolbar * lineHeight;
|
|
@@ -5801,6 +5875,8 @@ function PlacementTabs({
|
|
|
5801
5875
|
{
|
|
5802
5876
|
ref: containerRef,
|
|
5803
5877
|
className: `flex overflow-hidden text-sm leading-relaxed w-full ${className}`,
|
|
5878
|
+
"data-placement-tabs": true,
|
|
5879
|
+
"data-linked": dataLinked,
|
|
5804
5880
|
children: /* @__PURE__ */ (0, import_jsx_runtime50.jsxs)("p", { className: "m-0 w-full", children: [
|
|
5805
5881
|
/* @__PURE__ */ (0, import_jsx_runtime50.jsx)(
|
|
5806
5882
|
"span",
|
|
@@ -5819,15 +5895,7 @@ function PlacementTabs({
|
|
|
5819
5895
|
)
|
|
5820
5896
|
}
|
|
5821
5897
|
),
|
|
5822
|
-
|
|
5823
|
-
"button",
|
|
5824
|
-
{
|
|
5825
|
-
onClick: () => onPlacementChange(p.label),
|
|
5826
|
-
className: `inline transition-all duration-200 cursor-pointer mr-3 border-b-2 ${selectedPlacement === p.label ? "text-foreground border-primary" : "text-foreground/70 hover:text-foreground border-muted-foreground/20 hover:border-muted-foreground/40"}`,
|
|
5827
|
-
children: p.label
|
|
5828
|
-
},
|
|
5829
|
-
p.label
|
|
5830
|
-
))
|
|
5898
|
+
renderItems("inline mr-3")
|
|
5831
5899
|
] })
|
|
5832
5900
|
}
|
|
5833
5901
|
);
|
|
@@ -16554,6 +16622,7 @@ function usePersonalizationShimmer(containerRef, safetyTimeoutMs = 1e4) {
|
|
|
16554
16622
|
isWideMonitor,
|
|
16555
16623
|
kbdVariants,
|
|
16556
16624
|
linkVariants,
|
|
16625
|
+
linkedPlacementsLabel,
|
|
16557
16626
|
listProducts,
|
|
16558
16627
|
normalizeHex,
|
|
16559
16628
|
observe,
|