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.
Files changed (50) hide show
  1. package/dist/index.js +107 -17
  2. package/package.json +2 -2
  3. package/templates/blank/README.md +2 -1
  4. package/templates/blank/node_modules/.bin/alpic +2 -2
  5. package/templates/blank/node_modules/.bin/tsx +2 -2
  6. package/templates/blank/node_modules/.bin/vite +2 -2
  7. package/templates/blank/package.json +8 -7
  8. package/templates/blank/src/index.ts +3 -0
  9. package/templates/blank/src/server.ts +7 -11
  10. package/templates/blank/vite.config.ts +7 -1
  11. package/templates/demo/README.md +3 -1
  12. package/templates/demo/evals/start.eval.ts +11 -0
  13. package/templates/demo/node_modules/.bin/alpic +2 -2
  14. package/templates/demo/node_modules/.bin/tsx +2 -2
  15. package/templates/demo/node_modules/.bin/vite +2 -2
  16. package/templates/demo/node_modules/.bin/vitest +21 -0
  17. package/templates/demo/package.json +19 -13
  18. package/templates/demo/src/index.ts +3 -0
  19. package/templates/demo/src/server.ts +86 -88
  20. package/templates/demo/src/views/onboarding.tsx +3 -3
  21. package/templates/demo/tsconfig.evals.json +11 -0
  22. package/templates/demo/vite.config.ts +9 -3
  23. package/templates/ecom/.env.template +5 -0
  24. package/templates/ecom/README.md +2 -1
  25. package/templates/ecom/node_modules/.bin/alpic +2 -2
  26. package/templates/ecom/node_modules/.bin/ladle +2 -2
  27. package/templates/ecom/node_modules/.bin/vite +2 -2
  28. package/templates/ecom/package.json +3 -2
  29. package/templates/ecom/src/catalog/index.ts +19 -0
  30. package/templates/ecom/src/catalog/mock.ts +134 -0
  31. package/templates/ecom/src/catalog/shopify.ts +264 -0
  32. package/templates/ecom/src/components/chip.css.ts +15 -8
  33. package/templates/ecom/src/components/chip.stories.tsx +2 -1
  34. package/templates/ecom/src/components/chip.tsx +16 -8
  35. package/templates/ecom/src/components/product-card.tsx +2 -2
  36. package/templates/ecom/src/components/variant-picker.stories.tsx +7 -4
  37. package/templates/ecom/src/components/variant-picker.tsx +17 -29
  38. package/templates/ecom/src/components/view-frame.tsx +4 -4
  39. package/templates/ecom/src/i18n.ts +1 -1
  40. package/templates/ecom/src/index.ts +3 -0
  41. package/templates/ecom/src/lib/variants.ts +100 -29
  42. package/templates/ecom/src/server.ts +17 -21
  43. package/templates/ecom/src/tools/render-carousel.ts +5 -85
  44. package/templates/ecom/src/tools/search-products.ts +32 -21
  45. package/templates/ecom/src/types.ts +72 -0
  46. package/templates/ecom/src/views/carousel/detail/detail.css.ts +2 -2
  47. package/templates/ecom/src/views/carousel/detail/detail.stories.tsx +26 -8
  48. package/templates/ecom/src/views/carousel/detail/index.tsx +43 -29
  49. package/templates/ecom/src/views/carousel/index.tsx +1 -2
  50. 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 "../../../tools/render-carousel.js";
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
- outOfStock: boolean,
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 (outOfStock) {
81
- parts.push(labels.outOfStock);
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
- const variant = resolveVariant(product, selection);
102
- // Fall back to the card for display fields until a variant resolves.
103
- const displayTitle = variant?.title ?? product.card.title;
104
- const description = variant?.description ?? product.card.description;
105
- const media = variant?.media.length ? variant.media : product.card.media;
106
- const specs = variant?.specs ?? product.card.specs;
107
- const outOfStock = variant?.outOfStock ?? product.card.outOfStock ?? false;
108
- const url = variant?.url ?? product.card.url;
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 = variant?.id ?? product.id;
111
- // @todo: read any custom Meta fields you added the same way (variant first,
112
- // then card), e.g. `const rating = variant?.rating ?? product.card.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, variant?.price, locale, labels);
117
- // Buy CTA is enabled only once a variant resolves and carries a real link
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 = variant != null && Boolean(url);
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
- outOfStock,
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
- {variant ? labels.viewOnSite : labels.selectOptions}
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, index) => (
225
+ {specs.map((spec) => (
212
226
  <div
213
- key={`${spec.label ?? ""}:${spec.value}:${index}`}
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 "../../tools/render-carousel.js";
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({