create-skybridge 0.0.0-dev.94c22d0 → 0.0.0-dev.9520814
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/index.js +107 -17
- package/package.json +2 -2
- package/templates/blank/README.md +2 -1
- package/templates/blank/node_modules/.bin/alpic +2 -2
- package/templates/blank/node_modules/.bin/tsx +2 -2
- package/templates/blank/node_modules/.bin/vite +2 -2
- package/templates/blank/package.json +8 -7
- package/templates/blank/src/index.ts +3 -0
- package/templates/blank/src/server.ts +7 -11
- package/templates/blank/vite.config.ts +7 -1
- package/templates/demo/README.md +3 -1
- package/templates/demo/evals/start.eval.ts +11 -0
- package/templates/demo/node_modules/.bin/alpic +2 -2
- package/templates/demo/node_modules/.bin/tsx +2 -2
- package/templates/demo/node_modules/.bin/vite +2 -2
- package/templates/demo/node_modules/.bin/vitest +21 -0
- package/templates/demo/package.json +19 -13
- package/templates/demo/src/index.ts +3 -0
- package/templates/demo/src/server.ts +86 -88
- package/templates/demo/src/views/onboarding.tsx +3 -3
- package/templates/demo/tsconfig.evals.json +11 -0
- package/templates/demo/vite.config.ts +9 -3
- package/templates/ecom/.env.template +5 -0
- package/templates/ecom/README.md +2 -1
- package/templates/ecom/node_modules/.bin/alpic +2 -2
- package/templates/ecom/node_modules/.bin/ladle +2 -2
- package/templates/ecom/node_modules/.bin/vite +2 -2
- package/templates/ecom/package.json +3 -2
- package/templates/ecom/src/catalog/index.ts +19 -0
- package/templates/ecom/src/catalog/mock.ts +134 -0
- package/templates/ecom/src/catalog/shopify.ts +264 -0
- package/templates/ecom/src/components/chip.css.ts +15 -8
- package/templates/ecom/src/components/chip.stories.tsx +2 -1
- package/templates/ecom/src/components/chip.tsx +16 -8
- package/templates/ecom/src/components/product-card.tsx +2 -2
- package/templates/ecom/src/components/variant-picker.stories.tsx +7 -4
- package/templates/ecom/src/components/variant-picker.tsx +17 -29
- package/templates/ecom/src/components/view-frame.tsx +4 -4
- package/templates/ecom/src/i18n.ts +1 -1
- package/templates/ecom/src/index.ts +3 -0
- package/templates/ecom/src/lib/variants.ts +100 -29
- package/templates/ecom/src/server.ts +17 -21
- package/templates/ecom/src/tools/render-carousel.ts +5 -85
- package/templates/ecom/src/tools/search-products.ts +32 -21
- package/templates/ecom/src/types.ts +72 -0
- package/templates/ecom/src/views/carousel/detail/detail.css.ts +2 -2
- package/templates/ecom/src/views/carousel/detail/detail.stories.tsx +26 -8
- package/templates/ecom/src/views/carousel/detail/index.tsx +43 -29
- package/templates/ecom/src/views/carousel/index.tsx +1 -2
- package/templates/ecom/vite.config.ts +1 -1
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
resolveVariant,
|
|
13
13
|
type Selection,
|
|
14
14
|
} from "../../../lib/variants.js";
|
|
15
|
-
import type { Product } from "../../../
|
|
15
|
+
import type { Product, Variant } from "../../../types.js";
|
|
16
16
|
import * as styles from "./detail.css";
|
|
17
17
|
|
|
18
18
|
// Price to show: the resolved variant's price, else the range across variants
|
|
@@ -54,31 +54,42 @@ function priceText(
|
|
|
54
54
|
// scoped to the visible selection.
|
|
55
55
|
function grounding(
|
|
56
56
|
product: Product,
|
|
57
|
+
variant: Variant | undefined,
|
|
57
58
|
title: string,
|
|
58
59
|
price: string,
|
|
59
60
|
selection: Selection,
|
|
60
|
-
|
|
61
|
+
unpurchasable: boolean,
|
|
61
62
|
labels: Labels,
|
|
62
63
|
): string {
|
|
63
64
|
const parts = [`The user is viewing "${title}" (product id: ${product.id}).`];
|
|
64
65
|
const chosen: string[] = [];
|
|
65
66
|
for (const option of product.options) {
|
|
66
|
-
let label = "not selected";
|
|
67
67
|
const valueId = selection[option.id];
|
|
68
|
+
let label: string | undefined;
|
|
68
69
|
for (const value of option.values) {
|
|
69
70
|
if (value.id === valueId) {
|
|
70
71
|
label = value.label;
|
|
71
72
|
break;
|
|
72
73
|
}
|
|
73
74
|
}
|
|
75
|
+
if (label === undefined) {
|
|
76
|
+
// Axis skipped by the shown variant vs simply not picked yet.
|
|
77
|
+
label =
|
|
78
|
+
variant != null && variant.selection[option.id] == null
|
|
79
|
+
? "not applicable"
|
|
80
|
+
: "not selected";
|
|
81
|
+
}
|
|
74
82
|
chosen.push(`${option.label}: ${label}`);
|
|
75
83
|
}
|
|
76
84
|
if (chosen.length > 0) {
|
|
77
85
|
parts.push(`Selected — ${chosen.join(", ")}.`);
|
|
78
86
|
}
|
|
79
87
|
parts.push(`Price: ${price}.`);
|
|
80
|
-
if (
|
|
81
|
-
|
|
88
|
+
if (unpurchasable) {
|
|
89
|
+
// Sold-out real variant vs a combination that does not exist.
|
|
90
|
+
parts.push(
|
|
91
|
+
variant != null ? labels.outOfStock : labels.combinationUnavailable,
|
|
92
|
+
);
|
|
82
93
|
}
|
|
83
94
|
return parts.join(" ");
|
|
84
95
|
}
|
|
@@ -98,25 +109,27 @@ export function DetailView({ product }: { product: Product }) {
|
|
|
98
109
|
initialSelection(product),
|
|
99
110
|
);
|
|
100
111
|
|
|
101
|
-
|
|
102
|
-
//
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
|
|
112
|
+
// The exact variant for the selection; card fields fill in when none
|
|
113
|
+
// resolves (the applyChoice edge case, or a product with no variants).
|
|
114
|
+
const shown = resolveVariant(product, selection);
|
|
115
|
+
const displayTitle = shown?.title ?? product.card.title;
|
|
116
|
+
const description = shown?.description ?? product.card.description;
|
|
117
|
+
const media = shown?.media.length ? shown.media : product.card.media;
|
|
118
|
+
const specs = shown?.specs ?? product.card.specs;
|
|
119
|
+
// Sold out, or no variant resolved.
|
|
120
|
+
const unpurchasable = shown ? (shown.outOfStock ?? false) : true;
|
|
121
|
+
const url = shown?.url ?? product.card.url;
|
|
109
122
|
// The shown item's id: the resolved variant's SKU, else the product id.
|
|
110
|
-
const reference =
|
|
111
|
-
// @todo: read any custom Meta fields you added the same way (
|
|
112
|
-
// then card), e.g. `const rating =
|
|
123
|
+
const reference = shown?.id ?? product.id;
|
|
124
|
+
// @todo: read any custom Meta fields you added the same way (shown first,
|
|
125
|
+
// then card), e.g. `const rating = shown?.rating ?? product.card.rating;`,
|
|
113
126
|
// then render them in the agreed spot (rating by the title, discount by the
|
|
114
127
|
// price, badges as chips…).
|
|
115
128
|
|
|
116
|
-
const price = priceText(product,
|
|
117
|
-
// Buy CTA is enabled only
|
|
129
|
+
const price = priceText(product, shown?.price, locale, labels);
|
|
130
|
+
// Buy CTA is enabled only for an exact, in-stock variant with a real link
|
|
118
131
|
// (an empty url would leave the button enabled but dead).
|
|
119
|
-
const canBuy =
|
|
132
|
+
const canBuy = shown != null && Boolean(url) && !unpurchasable;
|
|
120
133
|
|
|
121
134
|
// Point the host's fullscreen "Open in app" affordance at the selected
|
|
122
135
|
// variant's page. Apps-SDK only; MCP Apps hosts reject, so ignore that.
|
|
@@ -131,10 +144,11 @@ export function DetailView({ product }: { product: Product }) {
|
|
|
131
144
|
className={styles.detail}
|
|
132
145
|
data-llm={grounding(
|
|
133
146
|
product,
|
|
147
|
+
shown,
|
|
134
148
|
displayTitle,
|
|
135
149
|
price,
|
|
136
150
|
selection,
|
|
137
|
-
|
|
151
|
+
unpurchasable,
|
|
138
152
|
labels,
|
|
139
153
|
)}
|
|
140
154
|
>
|
|
@@ -172,12 +186,6 @@ export function DetailView({ product }: { product: Product }) {
|
|
|
172
186
|
{price}
|
|
173
187
|
</p>
|
|
174
188
|
|
|
175
|
-
{outOfStock ? (
|
|
176
|
-
<p className={cx(text({ style: "bodyS" }), styles.oos)}>
|
|
177
|
-
{labels.outOfStock}
|
|
178
|
-
</p>
|
|
179
|
-
) : null}
|
|
180
|
-
|
|
181
189
|
<VariantPicker
|
|
182
190
|
product={product}
|
|
183
191
|
selection={selection}
|
|
@@ -186,13 +194,19 @@ export function DetailView({ product }: { product: Product }) {
|
|
|
186
194
|
|
|
187
195
|
{description ? <ExpandableText>{description}</ExpandableText> : null}
|
|
188
196
|
|
|
197
|
+
{/* Sold-out and unresolved selections stay composable; the CTA locks
|
|
198
|
+
and its label names the cause. */}
|
|
189
199
|
<button
|
|
190
200
|
type="button"
|
|
191
201
|
className={cx(styles.cta, sprinkles({ mt: "3xs" }))}
|
|
192
202
|
disabled={!canBuy}
|
|
193
203
|
onClick={canBuy && url ? () => openExternal(url) : undefined}
|
|
194
204
|
>
|
|
195
|
-
{
|
|
205
|
+
{shown
|
|
206
|
+
? unpurchasable
|
|
207
|
+
? labels.outOfStock
|
|
208
|
+
: labels.viewOnSite
|
|
209
|
+
: labels.combinationUnavailable}
|
|
196
210
|
</button>
|
|
197
211
|
|
|
198
212
|
{/* Product facts as a simple list, after the CTA: one "label: value"
|
|
@@ -208,9 +222,9 @@ export function DetailView({ product }: { product: Product }) {
|
|
|
208
222
|
{labels.specifications}
|
|
209
223
|
</h2>
|
|
210
224
|
<dl className={styles.specList}>
|
|
211
|
-
{specs.map((spec
|
|
225
|
+
{specs.map((spec) => (
|
|
212
226
|
<div
|
|
213
|
-
key={`${spec.label ?? ""}:${spec.value}
|
|
227
|
+
key={`${spec.label ?? ""}:${spec.value}`}
|
|
214
228
|
className={styles.specRow}
|
|
215
229
|
>
|
|
216
230
|
{spec.label ? (
|
|
@@ -20,8 +20,7 @@ import { sprinkles } from "../../design/tokens";
|
|
|
20
20
|
import { useToolInfo } from "../../helpers.js";
|
|
21
21
|
import { useLabels } from "../../i18n";
|
|
22
22
|
import { formatPrice } from "../../lib/format";
|
|
23
|
-
import type { Product } from "../../
|
|
24
|
-
import type { Price, Spec } from "../../types.js";
|
|
23
|
+
import type { Price, Product, Spec } from "../../types.js";
|
|
25
24
|
import { DetailView } from "./detail";
|
|
26
25
|
|
|
27
26
|
const SKELETON_COUNT = 4;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { skybridge } from "@skybridge/vite-plugin";
|
|
1
2
|
import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
|
|
2
3
|
import react from "@vitejs/plugin-react";
|
|
3
|
-
import { skybridge } from "skybridge/vite";
|
|
4
4
|
import { defineConfig, type PluginOption } from "vite";
|
|
5
5
|
|
|
6
6
|
export default defineConfig({
|