@thefittingroom/shop-ui 5.0.24 → 5.0.26

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 (3) hide show
  1. package/README.md +18 -1
  2. package/dist/index.js +385 -90
  3. package/package.json +8 -3
package/README.md CHANGED
@@ -18,13 +18,30 @@ npm ci
18
18
  | Script | What it does |
19
19
  |---|---|
20
20
  | `npm run build` | Clean + production build into `dist/` |
21
- | `npm run check` | Type-check (`tsc --noEmit`) |
21
+ | `npm run check` | Type-check (`tsc --noEmit`) + ESLint + Prettier |
22
22
  | `npm run watch` | Vite build in watch mode |
23
23
  | `npm run serve` | Static-serve the repo on `:5173` with CORS |
24
24
  | `npm run watch-serve` | Both `watch` and `serve` together; Ctrl+C stops both |
25
+ | `npm run test` / `npm run test:e2e` | Playwright e2e suite (Chromium) against the built bundle |
26
+ | `npm run test:e2e:ui` | Playwright UI runner for local debugging |
25
27
  | `npm run gen-types` | Regenerate `src/api/gen/*.ts` from `tfr-backend` Go types |
26
28
  | `npm run promote-latest [version]` | Move npm dist-tag `latest` onto a published version (defaults to current `package.json` version) |
27
29
 
30
+ ## Running e2e tests
31
+
32
+ First-time setup: `npm install && npx playwright install chromium` (the
33
+ second command downloads the Chromium binary once; cached afterwards). Then
34
+ `npm run test:e2e` runs the suite. Specs live under `tests/e2e/`; they boot
35
+ the built bundle in headless Chromium against a static fixture page
36
+ (`tests/e2e/fixtures/host.html`) that mimics the minimal contract the Shopify
37
+ theme provides. Firebase is mocked via `InitParams.testHooks`
38
+ (see `src/lib/firebase-mock.ts`); REST endpoints are mocked via
39
+ `page.route()`. See AGENTS.md for the architecture and constraints.
40
+
41
+ For an interactive debug loop, `npm run watch-serve &` in one terminal and
42
+ `npm run test:e2e:ui` in another — Playwright reuses the live-rebuilt
43
+ `:5173`.
44
+
28
45
  ## Release process
29
46
 
30
47
  Releases are explicit, human-initiated steps via a single GitHub Actions
package/dist/index.js CHANGED
@@ -14054,7 +14054,7 @@ function applyFrameBaseUrl(url, baseUrl2) {
14054
14054
  return `${cleanBase}${url.startsWith("/") ? "" : "/"}${url}`;
14055
14055
  }
14056
14056
  const STORAGE_KEY = "tfr:fitting-room:v1";
14057
- const logger$f = getLogger("fitting-room-storage");
14057
+ const logger$g = getLogger("fitting-room-storage");
14058
14058
  function readAll() {
14059
14059
  try {
14060
14060
  const raw = window.localStorage.getItem(STORAGE_KEY);
@@ -14067,7 +14067,7 @@ function readAll() {
14067
14067
  }
14068
14068
  return parsed;
14069
14069
  } catch (error) {
14070
- logger$f.logWarn("Failed to read fitting room from localStorage", {
14070
+ logger$g.logWarn("Failed to read fitting room from localStorage", {
14071
14071
  error
14072
14072
  });
14073
14073
  return {};
@@ -14077,7 +14077,7 @@ function writeAll(all) {
14077
14077
  try {
14078
14078
  window.localStorage.setItem(STORAGE_KEY, JSON.stringify(all));
14079
14079
  } catch (error) {
14080
- logger$f.logWarn("Failed to write fitting room to localStorage", {
14080
+ logger$g.logWarn("Failed to write fitting room to localStorage", {
14081
14081
  error
14082
14082
  });
14083
14083
  }
@@ -14103,7 +14103,7 @@ function _init$7() {
14103
14103
  }
14104
14104
  async function addFittingRoomItem(productId, handle, isPdp) {
14105
14105
  const state = useMainStore.getState();
14106
- logger$f.logDebug("{{ts}} - Adding to fitting room", {
14106
+ logger$g.logDebug("{{ts}} - Adding to fitting room", {
14107
14107
  productId,
14108
14108
  handle,
14109
14109
  isPdp
@@ -14125,7 +14125,7 @@ async function addFittingRoomItem(productId, handle, isPdp) {
14125
14125
  size = selection.size || null;
14126
14126
  color = selection.color;
14127
14127
  } catch (error) {
14128
- logger$f.logWarn("Failed to read selected options from currentProduct", {
14128
+ logger$g.logWarn("Failed to read selected options from currentProduct", {
14129
14129
  error
14130
14130
  });
14131
14131
  }
@@ -14157,7 +14157,7 @@ async function toggleFittingRoomItem(productId, handle, isPdp) {
14157
14157
  const state = useMainStore.getState();
14158
14158
  const isInFittingRoom = state.fittingRoom.some((item) => item.externalId === productId);
14159
14159
  if (isInFittingRoom) {
14160
- logger$f.logDebug("{{ts}} - Removing from fitting room", {
14160
+ logger$g.logDebug("{{ts}} - Removing from fitting room", {
14161
14161
  productId
14162
14162
  });
14163
14163
  state.removeFromFittingRoom(productId);
@@ -23058,7 +23058,7 @@ function isVersionServiceProvider(provider) {
23058
23058
  }
23059
23059
  const name$q = "@firebase/app";
23060
23060
  const version$1$1 = "0.14.5";
23061
- const logger$e = new Logger3("@firebase/app");
23061
+ const logger$f = new Logger3("@firebase/app");
23062
23062
  const name$p = "@firebase/app-compat";
23063
23063
  const name$o = "@firebase/analytics-compat";
23064
23064
  const name$n = "@firebase/analytics";
@@ -23125,13 +23125,13 @@ function _addComponent(app, component) {
23125
23125
  try {
23126
23126
  app.container.addComponent(component);
23127
23127
  } catch (e) {
23128
- logger$e.debug(`Component ${component.name} failed to register with FirebaseApp ${app.name}`, e);
23128
+ logger$f.debug(`Component ${component.name} failed to register with FirebaseApp ${app.name}`, e);
23129
23129
  }
23130
23130
  }
23131
23131
  function _registerComponent(component) {
23132
23132
  const componentName = component.name;
23133
23133
  if (_components.has(componentName)) {
23134
- logger$e.debug(`There were multiple attempts to register component ${componentName}.`);
23134
+ logger$f.debug(`There were multiple attempts to register component ${componentName}.`);
23135
23135
  return false;
23136
23136
  }
23137
23137
  _components.set(componentName, component);
@@ -23340,7 +23340,7 @@ function registerVersion(libraryKeyOrName, version2, variant) {
23340
23340
  if (versionMismatch) {
23341
23341
  warning.push(`version name "${version2}" contains illegal characters (whitespace or "/")`);
23342
23342
  }
23343
- logger$e.warn(warning.join(" "));
23343
+ logger$f.warn(warning.join(" "));
23344
23344
  return;
23345
23345
  }
23346
23346
  _registerComponent(new Component(
@@ -23384,12 +23384,12 @@ async function readHeartbeatsFromIndexedDB(app) {
23384
23384
  return result;
23385
23385
  } catch (e) {
23386
23386
  if (e instanceof FirebaseError) {
23387
- logger$e.warn(e.message);
23387
+ logger$f.warn(e.message);
23388
23388
  } else {
23389
23389
  const idbGetError = ERROR_FACTORY.create("idb-get", {
23390
23390
  originalErrorMessage: e?.message
23391
23391
  });
23392
- logger$e.warn(idbGetError.message);
23392
+ logger$f.warn(idbGetError.message);
23393
23393
  }
23394
23394
  }
23395
23395
  }
@@ -23402,12 +23402,12 @@ async function writeHeartbeatsToIndexedDB(app, heartbeatObject) {
23402
23402
  await tx.done;
23403
23403
  } catch (e) {
23404
23404
  if (e instanceof FirebaseError) {
23405
- logger$e.warn(e.message);
23405
+ logger$f.warn(e.message);
23406
23406
  } else {
23407
23407
  const idbGetError = ERROR_FACTORY.create("idb-set", {
23408
23408
  originalErrorMessage: e?.message
23409
23409
  });
23410
- logger$e.warn(idbGetError.message);
23410
+ logger$f.warn(idbGetError.message);
23411
23411
  }
23412
23412
  }
23413
23413
  }
@@ -23456,7 +23456,7 @@ class HeartbeatServiceImpl {
23456
23456
  }
23457
23457
  return this._storage.overwrite(this._heartbeatsCache);
23458
23458
  } catch (e) {
23459
- logger$e.warn(e);
23459
+ logger$f.warn(e);
23460
23460
  }
23461
23461
  }
23462
23462
  /**
@@ -23487,7 +23487,7 @@ class HeartbeatServiceImpl {
23487
23487
  }
23488
23488
  return headerString;
23489
23489
  } catch (e) {
23490
- logger$e.warn(e);
23490
+ logger$f.warn(e);
23491
23491
  return "";
23492
23492
  }
23493
23493
  }
@@ -41051,6 +41051,143 @@ registerAuth(
41051
41051
  "Browser"
41052
41052
  /* ClientPlatform.BROWSER */
41053
41053
  );
41054
+ const logger$e = getLogger("firebase-mock");
41055
+ class MockFirestoreManager {
41056
+ constructor(seedDocs = {}) {
41057
+ const cloned = {};
41058
+ for (const [coll, byId] of Object.entries(seedDocs)) {
41059
+ cloned[coll] = {
41060
+ ...byId
41061
+ };
41062
+ }
41063
+ this.docs = cloned;
41064
+ }
41065
+ async getDocData(collectionName, docId) {
41066
+ const doc2 = this.docs[collectionName]?.[docId];
41067
+ return doc2 ?? null;
41068
+ }
41069
+ async setDocData(collectionName, docId, data) {
41070
+ if (!this.docs[collectionName]) {
41071
+ this.docs[collectionName] = {};
41072
+ }
41073
+ this.docs[collectionName][docId] = data;
41074
+ }
41075
+ async mergeDocData(collectionName, docId, data) {
41076
+ if (!this.docs[collectionName]) {
41077
+ this.docs[collectionName] = {};
41078
+ }
41079
+ const existing = this.docs[collectionName][docId] ?? {};
41080
+ this.docs[collectionName][docId] = {
41081
+ ...existing,
41082
+ ...data
41083
+ };
41084
+ }
41085
+ listenToDoc(collectionName, docId, callback) {
41086
+ const doc2 = this.docs[collectionName]?.[docId];
41087
+ callback(doc2 ?? null);
41088
+ return () => {
41089
+ };
41090
+ }
41091
+ async queryDocs(collectionName, _constraints) {
41092
+ const entries = Object.values(this.docs[collectionName] ?? {});
41093
+ const docs = entries.map((data) => ({
41094
+ data: () => data
41095
+ }));
41096
+ const snapshot = {
41097
+ empty: docs.length === 0,
41098
+ docs,
41099
+ forEach: (cb) => docs.forEach(cb)
41100
+ };
41101
+ return snapshot;
41102
+ }
41103
+ }
41104
+ function makeMockAuthUser(seed) {
41105
+ const fake = {
41106
+ uid: seed.uid,
41107
+ email: seed.email,
41108
+ getIdToken: async (_forceRefresh) => seed.idToken
41109
+ };
41110
+ return fake;
41111
+ }
41112
+ class MockAuthManager {
41113
+ constructor(seed, firestore) {
41114
+ this.userProfile = null;
41115
+ this.authStateChangeListeners = /* @__PURE__ */ new Set();
41116
+ this.userProfileChangeListeners = /* @__PURE__ */ new Set();
41117
+ this.profileUnsub = null;
41118
+ this.seed = seed;
41119
+ this.currentUser = seed ? makeMockAuthUser(seed) : null;
41120
+ this.firestore = firestore;
41121
+ this.addAuthStateChangeListener((authUser) => this.handleAuthStateChanged(authUser));
41122
+ }
41123
+ addAuthStateChangeListener(callback) {
41124
+ this.authStateChangeListeners.add(callback);
41125
+ callback(this.currentUser);
41126
+ return () => {
41127
+ this.authStateChangeListeners.delete(callback);
41128
+ };
41129
+ }
41130
+ removeAuthStateChangeListener(callback) {
41131
+ this.authStateChangeListeners.delete(callback);
41132
+ }
41133
+ addUserProfileChangeListener(callback) {
41134
+ this.userProfileChangeListeners.add(callback);
41135
+ callback(this.userProfile);
41136
+ return () => {
41137
+ this.userProfileChangeListeners.delete(callback);
41138
+ };
41139
+ }
41140
+ removeUserProfileChangeListener(callback) {
41141
+ this.userProfileChangeListeners.delete(callback);
41142
+ }
41143
+ getAuthUser() {
41144
+ return this.currentUser;
41145
+ }
41146
+ async getAuthToken(_forceRefresh = false) {
41147
+ if (!this.currentUser || !this.seed) {
41148
+ throw new Error("No authenticated user");
41149
+ }
41150
+ return this.seed.idToken;
41151
+ }
41152
+ async getUserProfile(_forceRefresh = false) {
41153
+ return this.userProfile;
41154
+ }
41155
+ async login(_email, _password) {
41156
+ throw new Error("MockAuthManager.login is not supported — seed the user via InitParams.testHooks.auth");
41157
+ }
41158
+ async logout() {
41159
+ if (this.profileUnsub) {
41160
+ this.profileUnsub();
41161
+ this.profileUnsub = null;
41162
+ }
41163
+ this.currentUser = null;
41164
+ this.userProfile = null;
41165
+ this.authStateChangeListeners.forEach((cb) => cb(null));
41166
+ this.userProfileChangeListeners.forEach((cb) => cb(null));
41167
+ }
41168
+ async sendPasswordResetEmail(_email) {
41169
+ }
41170
+ async confirmPasswordReset(_code, _newPassword) {
41171
+ }
41172
+ handleAuthStateChanged(authUser) {
41173
+ if (this.profileUnsub) {
41174
+ this.profileUnsub();
41175
+ this.profileUnsub = null;
41176
+ }
41177
+ if (authUser) {
41178
+ logger$e.logDebug("Mock user logged in:", {
41179
+ uid: authUser.uid
41180
+ });
41181
+ this.profileUnsub = this.firestore.listenToDoc("users", authUser.uid, (profile) => {
41182
+ this.userProfile = profile;
41183
+ this.userProfileChangeListeners.forEach((cb) => cb(this.userProfile));
41184
+ });
41185
+ } else {
41186
+ this.userProfile = null;
41187
+ this.userProfileChangeListeners.forEach((cb) => cb(null));
41188
+ }
41189
+ }
41190
+ }
41054
41191
  const firebaseDateToDayjs = (date) => {
41055
41192
  return dayjs(date.seconds * 1e3);
41056
41193
  };
@@ -41238,8 +41375,24 @@ function getAuthManager() {
41238
41375
  async function _init$4() {
41239
41376
  const {
41240
41377
  brandId,
41241
- config
41378
+ config,
41379
+ testHooks
41242
41380
  } = getStaticData();
41381
+ if (testHooks !== void 0) {
41382
+ const seedDocs = {
41383
+ ...testHooks.firestore?.docs ?? {}
41384
+ };
41385
+ if (testHooks.auth?.profile) {
41386
+ seedDocs.users = {
41387
+ ...seedDocs.users ?? {},
41388
+ [testHooks.auth.uid]: testHooks.auth.profile
41389
+ };
41390
+ }
41391
+ firestoreManager = new MockFirestoreManager(seedDocs);
41392
+ authManager = new MockAuthManager(testHooks.auth ?? null, firestoreManager);
41393
+ logger$d.logDebug("Firebase initialized in MOCK mode (testHooks present)");
41394
+ return;
41395
+ }
41243
41396
  {
41244
41397
  const {
41245
41398
  firebase: sdkFirebaseConfig
@@ -42644,6 +42797,35 @@ function AddToCartButton({
42644
42797
  }) {
42645
42798
  return /* @__PURE__ */ jsx$1(ButtonT, { variant: "brand", t: "quick-view.add_to_cart", onClick });
42646
42799
  }
42800
+ function ColorSelector({
42801
+ availableColorLabels,
42802
+ selectedColorLabel,
42803
+ onChangeColor
42804
+ }) {
42805
+ const css2 = useCss((theme) => ({
42806
+ colorContainer: {},
42807
+ colorLabelText: {
42808
+ fontSize: "12px"
42809
+ },
42810
+ colorSelect: {
42811
+ border: "none",
42812
+ color: theme.color_fg_text,
42813
+ fontFamily: theme.font_family,
42814
+ fontSize: "12px"
42815
+ }
42816
+ }));
42817
+ const handleColorSelectChange = reactExports.useCallback((e) => {
42818
+ const newColorLabel = e.target.value || null;
42819
+ onChangeColor(newColorLabel);
42820
+ }, [onChangeColor]);
42821
+ if (availableColorLabels.length < 2) {
42822
+ return null;
42823
+ }
42824
+ return /* @__PURE__ */ jsx$1("div", { css: css2.colorContainer, children: /* @__PURE__ */ jsxs("label", { children: [
42825
+ /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.colorLabelText, t: "quick-view.color_label" }),
42826
+ /* @__PURE__ */ jsx$1("select", { value: selectedColorLabel ?? "", onChange: handleColorSelectChange, css: css2.colorSelect, children: availableColorLabels.map((colorLabel) => /* @__PURE__ */ jsx$1("option", { value: colorLabel, children: colorLabel }, colorLabel)) })
42827
+ ] }) });
42828
+ }
42647
42829
  function ItemFitDetails({
42648
42830
  loadedProductData,
42649
42831
  selectedSizeLabel
@@ -42693,7 +42875,7 @@ function ItemFitDetails({
42693
42875
  ] }) : fitLabel })
42694
42876
  ] }, index);
42695
42877
  });
42696
- }, [loadedProductData, selectedSizeLabel]);
42878
+ }, [loadedProductData, selectedSizeLabel, t, css2.detailCell, css2.firstLine, css2.line]);
42697
42879
  return /* @__PURE__ */ jsx$1("div", { css: css2.container, children: fitLineNodeList });
42698
42880
  }
42699
42881
  function ItemFitText({
@@ -42740,7 +42922,7 @@ function SizeSelector({
42740
42922
  }
42741
42923
  onChangeSize(sizeRecord.sizeLabel);
42742
42924
  }, children: sizeRecord.sizeLabel }, sizeRecord.sizeLabel);
42743
- }), [loadedProductData.sizes, selectedSizeLabel, onChangeSize]);
42925
+ }), [loadedProductData.sizes, selectedSizeLabel, onChangeSize, css2.button, css2.selectedButton]);
42744
42926
  return /* @__PURE__ */ jsx$1("div", { css: css2.container, children: sizeSelectorNodeList });
42745
42927
  }
42746
42928
  function DetailAccordionItem({
@@ -42754,6 +42936,7 @@ function DetailAccordionItem({
42754
42936
  onToggleOpen,
42755
42937
  onChangeDetailMode,
42756
42938
  onChangeSize,
42939
+ onChangeColor,
42757
42940
  onAddToCart,
42758
42941
  onToggleUntuck
42759
42942
  }) {
@@ -42789,13 +42972,23 @@ function DetailAccordionItem({
42789
42972
  }
42790
42973
  return null;
42791
42974
  }, [productData, item.storage.colorwaySizeAssetId]);
42975
+ const availableColorLabels = reactExports.useMemo(() => {
42976
+ if (!productData || !selectedSizeLabel) {
42977
+ return [];
42978
+ }
42979
+ const sizeRec = productData.sizes.find((s) => s.sizeLabel === selectedSizeLabel);
42980
+ if (!sizeRec) {
42981
+ return [];
42982
+ }
42983
+ return sizeRec.colors.map((c) => c.colorLabel).filter((label) => !!label);
42984
+ }, [productData, selectedSizeLabel]);
42792
42985
  const categoryLabel = item.styleCategory?.label_singular ?? item.styleCategory?.label ?? "";
42793
42986
  const productName = item.merchantProduct?.productName ?? item.externalId;
42794
42987
  const tuckable = !!item.styleCategory?.tuckable && canTuck;
42795
42988
  if (platform === "desktop") {
42796
- return /* @__PURE__ */ jsx$1(DesktopAccordionItem, { isOpen, categoryLabel, productData, currentPrice, selectedSizeLabel, onToggleOpen, onChangeSize, onAddToCart });
42989
+ return /* @__PURE__ */ jsx$1(DesktopAccordionItem, { isOpen, categoryLabel, productData, currentPrice, selectedSizeLabel, availableColorLabels, selectedColorLabel: item.storage.color, onToggleOpen, onChangeSize, onChangeColor, onAddToCart });
42797
42990
  }
42798
- return /* @__PURE__ */ jsx$1(MobileAccordionItem, { isOpen, categoryLabel, productName, productData, selectedSizeLabel, currentPrice, detailMode, isMobileQuickRow, tuckable, forceUntuck, onToggleOpen, onChangeDetailMode, onChangeSize, onAddToCart, onToggleUntuck });
42991
+ return /* @__PURE__ */ jsx$1(MobileAccordionItem, { isOpen, categoryLabel, productName, productData, selectedSizeLabel, availableColorLabels, selectedColorLabel: item.storage.color, currentPrice, detailMode, isMobileQuickRow, tuckable, forceUntuck, onToggleOpen, onChangeDetailMode, onChangeSize, onChangeColor, onAddToCart, onToggleUntuck });
42799
42992
  }
42800
42993
  function DesktopAccordionItem({
42801
42994
  isOpen,
@@ -42803,8 +42996,11 @@ function DesktopAccordionItem({
42803
42996
  productData,
42804
42997
  currentPrice,
42805
42998
  selectedSizeLabel,
42999
+ availableColorLabels,
43000
+ selectedColorLabel,
42806
43001
  onToggleOpen,
42807
43002
  onChangeSize,
43003
+ onChangeColor,
42808
43004
  onAddToCart
42809
43005
  }) {
42810
43006
  const ACCORDION_SHADE = "#F4F4F4";
@@ -42823,8 +43019,10 @@ function DesktopAccordionItem({
42823
43019
  backgroundColor: ACCORDION_SHADE
42824
43020
  },
42825
43021
  categoryLabel: {
42826
- fontSize: "16px",
42827
- fontWeight: "400"
43022
+ fontFamily: "'Times New Roman', serif",
43023
+ fontSize: "20px",
43024
+ fontWeight: "400",
43025
+ letterSpacing: "0.04em"
42828
43026
  },
42829
43027
  chevron: {
42830
43028
  display: "inline-flex",
@@ -42845,40 +43043,72 @@ function DesktopAccordionItem({
42845
43043
  },
42846
43044
  productName: {
42847
43045
  fontSize: "24px",
43046
+ fontWeight: "300",
42848
43047
  lineHeight: "1.2"
42849
43048
  },
42850
43049
  price: {
42851
43050
  fontSize: "15px"
42852
43051
  },
43052
+ // Padding matches quick-view's sizeRecommendationFrame (32px / 56px) so
43053
+ // the "fit box" feels visually consistent between the two overlays.
43054
+ //
43055
+ // No flex `gap` — the three text lines (recommended size, fit text,
43056
+ // select-a-size prompt) sit tight against each other (matching
43057
+ // quick-view), with explicit marginTop on the size selector + fit
43058
+ // details below them to introduce the larger break.
42853
43059
  sizeBox: {
42854
43060
  width: "100%",
42855
43061
  border: `1px solid ${theme.color_fg_text}`,
42856
- padding: "20px 24px",
43062
+ padding: "32px 56px",
42857
43063
  display: "flex",
42858
43064
  flexDirection: "column",
42859
43065
  alignItems: "center",
42860
- gap: "12px",
42861
43066
  marginTop: "8px",
42862
43067
  textAlign: "center"
42863
43068
  },
43069
+ colorSelectorContainer: {
43070
+ width: "100%",
43071
+ marginTop: "8px"
43072
+ },
43073
+ // 14px / line-height 1.5 on these three text lines matches quick-view's
43074
+ // fit-box. Quick-view's first line wraps an InfoIcon button alongside
43075
+ // the recommended-size text, which stretches that line vertically; the
43076
+ // simpler line-height bump here matches the *visual* line spacing
43077
+ // without dragging the icon in. The two lines below it inherit
43078
+ // line-height from their containers in quick-view, which the host page's
43079
+ // body styles tend to set looser than Inter's intrinsic `normal` (~1.21).
42864
43080
  recommendedSize: {
42865
- fontSize: "13px",
42866
- fontWeight: "600"
43081
+ fontSize: "14px",
43082
+ fontWeight: "600",
43083
+ lineHeight: 1.5
42867
43084
  },
42868
43085
  selectPrompt: {
42869
- fontSize: "12px"
43086
+ fontSize: "14px",
43087
+ fontWeight: "300",
43088
+ lineHeight: 1.5
42870
43089
  },
42871
43090
  fitText: {
42872
- fontSize: "12px"
43091
+ fontSize: "14px",
43092
+ fontWeight: "300",
43093
+ lineHeight: 1.5,
43094
+ // Tight 8px lift to the recommended-size line above; matches
43095
+ // quick-view's `itemFitContainer` marginTop.
43096
+ marginTop: "8px"
42873
43097
  },
42874
43098
  fitDetails: {
42875
- width: "100%"
43099
+ width: "100%",
43100
+ // Cascades to the text inside <ItemFitDetails>; the size-selector
43101
+ // buttons and the bold recommended-size line above stay at their
43102
+ // own weights.
43103
+ fontWeight: 300,
43104
+ marginTop: "24px"
42876
43105
  },
42877
43106
  sizeRow: {
42878
43107
  display: "flex",
42879
43108
  gap: "8px",
42880
43109
  alignItems: "center",
42881
- justifyContent: "center"
43110
+ justifyContent: "center",
43111
+ marginTop: "24px"
42882
43112
  },
42883
43113
  cartContainer: {
42884
43114
  width: "100%",
@@ -42899,6 +43129,7 @@ function DesktopAccordionItem({
42899
43129
  !isOpen ? null : /* @__PURE__ */ jsx$1("div", { css: css2.body, children: productData ? /* @__PURE__ */ jsxs(Fragment, { children: [
42900
43130
  /* @__PURE__ */ jsx$1(Text, { variant: "brand", css: css2.productName, children: productData.productName }),
42901
43131
  currentPrice ? /* @__PURE__ */ jsx$1(Text, { variant: "base", css: css2.price, children: currentPrice }) : null,
43132
+ /* @__PURE__ */ jsx$1("div", { css: css2.colorSelectorContainer, children: /* @__PURE__ */ jsx$1(ColorSelector, { availableColorLabels, selectedColorLabel, onChangeColor }) }),
42902
43133
  /* @__PURE__ */ jsxs("div", { css: css2.sizeBox, children: [
42903
43134
  /* @__PURE__ */ jsxs(Text, { variant: "base", css: css2.recommendedSize, children: [
42904
43135
  "Recommended Size: ",
@@ -42922,6 +43153,8 @@ function MobileAccordionItem({
42922
43153
  productName,
42923
43154
  productData,
42924
43155
  selectedSizeLabel,
43156
+ availableColorLabels,
43157
+ selectedColorLabel,
42925
43158
  currentPrice,
42926
43159
  detailMode,
42927
43160
  isMobileQuickRow,
@@ -42930,6 +43163,7 @@ function MobileAccordionItem({
42930
43163
  onToggleOpen,
42931
43164
  onChangeDetailMode,
42932
43165
  onChangeSize,
43166
+ onChangeColor,
42933
43167
  onAddToCart,
42934
43168
  onToggleUntuck
42935
43169
  }) {
@@ -42962,12 +43196,16 @@ function MobileAccordionItem({
42962
43196
  minWidth: 0
42963
43197
  },
42964
43198
  categoryLabel: {
42965
- fontSize: "15px",
43199
+ fontFamily: "'Times New Roman', serif",
43200
+ fontSize: "16px",
42966
43201
  fontWeight: "400",
43202
+ letterSpacing: "0.04em",
42967
43203
  flex: "none"
42968
43204
  },
42969
43205
  productName: {
42970
- fontSize: "15px",
43206
+ fontFamily: "'Times New Roman', serif",
43207
+ fontSize: "16px",
43208
+ letterSpacing: "0.04em",
42971
43209
  color: "#8A8A8A",
42972
43210
  overflow: "hidden",
42973
43211
  textOverflow: "ellipsis",
@@ -43069,6 +43307,7 @@ function MobileAccordionItem({
43069
43307
  ] }),
43070
43308
  isMobileQuickRow ? /* @__PURE__ */ jsx$1("div", { css: css2.content, children: /* @__PURE__ */ jsx$1("div", { css: css2.quickRow, children: productData ? /* @__PURE__ */ jsx$1(SizeSelector, { loadedProductData: productData, selectedSizeLabel, onChangeSize }) : null }) }) : !isOpen ? null : /* @__PURE__ */ jsx$1("div", { css: css2.content, children: /* @__PURE__ */ jsx$1("div", { css: css2.body, children: productData ? /* @__PURE__ */ jsxs(Fragment, { children: [
43071
43309
  /* @__PURE__ */ jsx$1("div", { css: css2.sizeRow, children: /* @__PURE__ */ jsx$1(SizeSelector, { loadedProductData: productData, selectedSizeLabel, onChangeSize }) }),
43310
+ /* @__PURE__ */ jsx$1(ColorSelector, { availableColorLabels, selectedColorLabel, onChangeColor }),
43072
43311
  /* @__PURE__ */ jsx$1(ItemFitText, { loadedProductData: productData }),
43073
43312
  /* @__PURE__ */ jsx$1("div", { css: css2.fitDetailsContainer, children: /* @__PURE__ */ jsx$1(ItemFitDetails, { loadedProductData: productData, selectedSizeLabel }) }),
43074
43313
  /* @__PURE__ */ jsx$1("div", { css: css2.buttonContainer, children: /* @__PURE__ */ jsx$1(AddToCartButton, { onClick: onAddToCart }) }),
@@ -43095,6 +43334,7 @@ function DetailAccordion({
43095
43334
  onOpenItem,
43096
43335
  onChangeDetailMode,
43097
43336
  onChangeSize,
43337
+ onChangeColor,
43098
43338
  onAddToCart,
43099
43339
  onToggleUntuck
43100
43340
  }) {
@@ -43109,9 +43349,11 @@ function DetailAccordion({
43109
43349
  gap
43110
43350
  }, children: items.map((item) => {
43111
43351
  const isOpen = openItemExternalId === item.externalId;
43112
- return /* @__PURE__ */ jsx$1(DetailAccordionItem, { item, isOpen, platform, detailMode, isMobileQuickRow, forceUntuck, canTuck, onToggleOpen: () => onOpenItem(isOpen ? null : item.externalId), onChangeDetailMode, onChangeSize: (label) => onChangeSize(item.externalId, label), onAddToCart: () => onAddToCart(item.externalId), onToggleUntuck }, item.externalId);
43352
+ return /* @__PURE__ */ jsx$1(DetailAccordionItem, { item, isOpen, platform, detailMode, isMobileQuickRow, forceUntuck, canTuck, onToggleOpen: () => onOpenItem(isOpen ? null : item.externalId), onChangeDetailMode, onChangeSize: (label) => onChangeSize(item.externalId, label), onChangeColor: (label) => onChangeColor(item.externalId, label), onAddToCart: () => onAddToCart(item.externalId), onToggleUntuck }, item.externalId);
43113
43353
  }) });
43114
43354
  }
43355
+ const AXIS_LOCK_PX = 8;
43356
+ const ROTATE_STEP_PX = 50;
43115
43357
  function ZoomModal({
43116
43358
  frameUrls,
43117
43359
  selectedFrameIndex,
@@ -43131,10 +43373,50 @@ function ZoomModal({
43131
43373
  }, [onClose]);
43132
43374
  const {
43133
43375
  rotateLeft,
43134
- rotateRight,
43135
- handleMouseDragStart,
43136
- handleTouchDragStart
43376
+ rotateRight
43137
43377
  } = useFrameRotation(frameUrls, setSelectedFrameIndex);
43378
+ const scrollAreaRef = reactExports.useRef(null);
43379
+ const handleImageMouseDown = reactExports.useCallback((e) => {
43380
+ e.preventDefault();
43381
+ const startX = e.clientX;
43382
+ const startY = e.clientY;
43383
+ const scrollArea = scrollAreaRef.current;
43384
+ const startScrollTop = scrollArea?.scrollTop ?? 0;
43385
+ let mode = "unknown";
43386
+ let lastRotateX = startX;
43387
+ const onMove = (move) => {
43388
+ const deltaX = move.clientX - startX;
43389
+ const deltaY = move.clientY - startY;
43390
+ if (mode === "unknown") {
43391
+ const absX = Math.abs(deltaX);
43392
+ const absY = Math.abs(deltaY);
43393
+ if (absX < AXIS_LOCK_PX && absY < AXIS_LOCK_PX) {
43394
+ return;
43395
+ }
43396
+ mode = absY > absX ? "scroll" : "rotate";
43397
+ lastRotateX = move.clientX;
43398
+ }
43399
+ if (mode === "scroll" && scrollArea) {
43400
+ scrollArea.scrollTop = startScrollTop - deltaY;
43401
+ } else if (mode === "rotate") {
43402
+ const rotateDelta = move.clientX - lastRotateX;
43403
+ if (Math.abs(rotateDelta) >= ROTATE_STEP_PX) {
43404
+ if (rotateDelta > 0) {
43405
+ rotateRight();
43406
+ } else {
43407
+ rotateLeft();
43408
+ }
43409
+ lastRotateX = move.clientX;
43410
+ }
43411
+ }
43412
+ };
43413
+ const onUp = () => {
43414
+ window.removeEventListener("mousemove", onMove);
43415
+ window.removeEventListener("mouseup", onUp);
43416
+ };
43417
+ window.addEventListener("mousemove", onMove);
43418
+ window.addEventListener("mouseup", onUp);
43419
+ }, [rotateLeft, rotateRight]);
43138
43420
  const css2 = useCss((_theme) => ({
43139
43421
  backdrop: {
43140
43422
  position: "fixed",
@@ -43213,7 +43495,7 @@ function ZoomModal({
43213
43495
  }));
43214
43496
  const imageUrl = frameUrls[selectedFrameIndex ?? 0];
43215
43497
  return /* @__PURE__ */ jsxs("div", { css: css2.backdrop, children: [
43216
- /* @__PURE__ */ jsx$1("div", { css: css2.scrollArea, children: /* @__PURE__ */ jsx$1("div", { css: css2.imageWrap, children: /* @__PURE__ */ jsx$1("img", { src: imageUrl, css: css2.image, alt: "", onMouseDown: handleMouseDragStart, onTouchStart: handleTouchDragStart }) }) }),
43498
+ /* @__PURE__ */ jsx$1("div", { ref: scrollAreaRef, css: css2.scrollArea, children: /* @__PURE__ */ jsx$1("div", { css: css2.imageWrap, children: /* @__PURE__ */ jsx$1("img", { src: imageUrl, css: css2.image, alt: "", onMouseDown: handleImageMouseDown }) }) }),
43217
43499
  /* @__PURE__ */ jsx$1("div", { css: /* @__PURE__ */ css$1({
43218
43500
  ...css2.chevron,
43219
43501
  ...css2.chevronLeft
@@ -43246,6 +43528,7 @@ function DesktopLayout$1({
43246
43528
  onOpenAccordionItem,
43247
43529
  onChangeDetailMode,
43248
43530
  onChangeSize,
43531
+ onChangeColor,
43249
43532
  onAddToCart,
43250
43533
  onToggleUntuck,
43251
43534
  onSignOut
@@ -43342,7 +43625,7 @@ function DesktopLayout$1({
43342
43625
  gridTemplateColumns
43343
43626
  }, children: [
43344
43627
  /* @__PURE__ */ jsx$1("div", { css: css2.avatarColumn, onMouseEnter: () => setAvatarHovered(true), onMouseLeave: () => setAvatarHovered(false), children: /* @__PURE__ */ jsx$1(AvatarPane, { hasSelection, frameUrls, controls, selectedFrameIndex, setSelectedFrameIndex }) }),
43345
- hasSelection ? /* @__PURE__ */ jsx$1("div", { css: css2.detailColumn, children: /* @__PURE__ */ jsx$1(DetailAccordion, { items: selectedItems, openItemExternalId: openAccordionItemId, platform: "desktop", detailMode, isMobileQuickRow: false, forceUntuck, canTuck, onOpenItem: onOpenAccordionItem, onChangeDetailMode, onChangeSize, onAddToCart, onToggleUntuck }) }) : null,
43628
+ hasSelection ? /* @__PURE__ */ jsx$1("div", { css: css2.detailColumn, children: /* @__PURE__ */ jsx$1(DetailAccordion, { items: selectedItems, openItemExternalId: openAccordionItemId, platform: "desktop", detailMode, isMobileQuickRow: false, forceUntuck, canTuck, onOpenItem: onOpenAccordionItem, onChangeDetailMode, onChangeSize, onChangeColor, onAddToCart, onToggleUntuck }) }) : null,
43346
43629
  /* @__PURE__ */ jsxs("div", { css: css2.railsColumn, children: [
43347
43630
  /* @__PURE__ */ jsxs("span", { css: css2.signOutWrapper, onClick: onSignOut, children: [
43348
43631
  /* @__PURE__ */ jsx$1(SvgTfrIcon, { css: css2.signOutIcon }),
@@ -43477,13 +43760,14 @@ function MobileLayout$1({
43477
43760
  onOpenAccordionItem,
43478
43761
  onChangeDetailMode,
43479
43762
  onChangeSize,
43763
+ onChangeColor,
43480
43764
  onAddToCart,
43481
43765
  onToggleUntuck
43482
43766
  }) {
43483
43767
  if (mode === "browse") {
43484
43768
  return /* @__PURE__ */ jsx$1(BrowseView, { resolved, availabilityByExternalId, selectedCount: selectedItems.length, onSelectItem, onRemoveItem, onTryItOn });
43485
43769
  }
43486
- return /* @__PURE__ */ jsx$1(TryOnView, { selectedItems, openAccordionItemId, detailMode, forceUntuck, canTuck, frameUrls, sheetSnap, sheetTouchStart, onBackToBrowse, onOpenAccordionItem, onChangeDetailMode, onChangeSize, onAddToCart, onToggleUntuck });
43770
+ return /* @__PURE__ */ jsx$1(TryOnView, { selectedItems, openAccordionItemId, detailMode, forceUntuck, canTuck, frameUrls, sheetSnap, sheetTouchStart, onBackToBrowse, onOpenAccordionItem, onChangeDetailMode, onChangeSize, onChangeColor, onAddToCart, onToggleUntuck });
43487
43771
  }
43488
43772
  function BrowseView({
43489
43773
  resolved,
@@ -43583,6 +43867,7 @@ function TryOnView({
43583
43867
  onOpenAccordionItem,
43584
43868
  onChangeDetailMode,
43585
43869
  onChangeSize,
43870
+ onChangeColor,
43586
43871
  onAddToCart,
43587
43872
  onToggleUntuck
43588
43873
  }) {
@@ -43683,7 +43968,7 @@ function TryOnView({
43683
43968
  /* @__PURE__ */ jsx$1(SvgDragHandle, {}),
43684
43969
  /* @__PURE__ */ jsx$1(Text, { variant: "base", css: css2.sheetTitle, children: "RECOMMENDED SIZES" })
43685
43970
  ] }),
43686
- sheetSnap === "collapsed" ? null : /* @__PURE__ */ jsx$1("div", { css: css2.sheetContent, children: /* @__PURE__ */ jsx$1(DetailAccordion, { items: selectedItems, openItemExternalId: openAccordionItemId, platform: "mobile", detailMode, isMobileQuickRow, forceUntuck, canTuck, onOpenItem: onOpenAccordionItem, onChangeDetailMode, onChangeSize, onAddToCart, onToggleUntuck }) })
43971
+ sheetSnap === "collapsed" ? null : /* @__PURE__ */ jsx$1("div", { css: css2.sheetContent, children: /* @__PURE__ */ jsx$1(DetailAccordion, { items: selectedItems, openItemExternalId: openAccordionItemId, platform: "mobile", detailMode, isMobileQuickRow, forceUntuck, canTuck, onOpenItem: onOpenAccordionItem, onChangeDetailMode, onChangeSize, onChangeColor, onAddToCart, onToggleUntuck }) })
43687
43972
  ] }) })
43688
43973
  ] });
43689
43974
  }
@@ -43956,6 +44241,25 @@ function FittingRoomOverlay({
43956
44241
  color: csa.colorLabel
43957
44242
  });
43958
44243
  }, [resolved.items, updateFittingRoomItem]);
44244
+ const handleChangeColor = reactExports.useCallback((externalId, colorLabel) => {
44245
+ const item = resolved.items.find((i) => i.externalId === externalId);
44246
+ if (!item || !item.storage.size) {
44247
+ return;
44248
+ }
44249
+ const productData = buildVtoProductDataFromResolved(item);
44250
+ if (!productData) {
44251
+ return;
44252
+ }
44253
+ const csa = findCsaByLabel(productData, item.storage.size, colorLabel);
44254
+ if (!csa) {
44255
+ return;
44256
+ }
44257
+ updateFittingRoomItem(externalId, {
44258
+ colorwaySizeAssetId: csa.colorwaySizeAssetId,
44259
+ size: item.storage.size,
44260
+ color: csa.colorLabel
44261
+ });
44262
+ }, [resolved.items, updateFittingRoomItem]);
43959
44263
  const handleAddToCart = reactExports.useCallback(async (externalId) => {
43960
44264
  const {
43961
44265
  addToCart
@@ -44148,7 +44452,7 @@ function FittingRoomOverlay({
44148
44452
  /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.emptyTagline, t: "landing.description" }),
44149
44453
  /* @__PURE__ */ jsx$1("div", { css: css2.emptyShopNow, children: /* @__PURE__ */ jsx$1(ButtonT, { variant: "primary", t: "fitting_room.shop_now", onClick: handleShopNow }) }),
44150
44454
  userIsLoggedIn ? /* @__PURE__ */ jsx$1(LinkT, { variant: "underline", css: css2.emptySignOut, t: "fitting_room.sign_out", onClick: handleSignOut }) : null
44151
- ] }) }) : isMobileLayout ? /* @__PURE__ */ jsx$1(MobileLayout$1, { mode: mobileMode, resolved, selectedItems, availabilityByExternalId, openAccordionItemId, detailMode, forceUntuck, canTuck, frameUrls, sheetSnap, sheetTouchStart, onSelectItem: handleSelectItem, onRemoveItem: handleRemoveItem, onTryItOn: handleTryItOn, onBackToBrowse: handleBackToBrowse, onOpenAccordionItem: setOpenAccordionItemId, onChangeDetailMode: setDetailMode, onChangeSize: handleChangeSize, onAddToCart: handleAddToCart, onToggleUntuck: handleToggleUntuck }) : /* @__PURE__ */ jsx$1(DesktopLayout$1, { resolved, selectedItems, availabilityByExternalId, openAccordionItemId, detailMode, forceUntuck, canTuck, frameUrls, onSelectItem: handleSelectItem, onRemoveItem: handleRemoveItem, onOpenAccordionItem: setOpenAccordionItemId, onChangeDetailMode: setDetailMode, onChangeSize: handleChangeSize, onAddToCart: handleAddToCart, onToggleUntuck: handleToggleUntuck, onSignOut: handleSignOut }),
44455
+ ] }) }) : isMobileLayout ? /* @__PURE__ */ jsx$1(MobileLayout$1, { mode: mobileMode, resolved, selectedItems, availabilityByExternalId, openAccordionItemId, detailMode, forceUntuck, canTuck, frameUrls, sheetSnap, sheetTouchStart, onSelectItem: handleSelectItem, onRemoveItem: handleRemoveItem, onTryItOn: handleTryItOn, onBackToBrowse: handleBackToBrowse, onOpenAccordionItem: setOpenAccordionItemId, onChangeDetailMode: setDetailMode, onChangeSize: handleChangeSize, onChangeColor: handleChangeColor, onAddToCart: handleAddToCart, onToggleUntuck: handleToggleUntuck }) : /* @__PURE__ */ jsx$1(DesktopLayout$1, { resolved, selectedItems, availabilityByExternalId, openAccordionItemId, detailMode, forceUntuck, canTuck, frameUrls, onSelectItem: handleSelectItem, onRemoveItem: handleRemoveItem, onOpenAccordionItem: setOpenAccordionItemId, onChangeDetailMode: setDetailMode, onChangeSize: handleChangeSize, onChangeColor: handleChangeColor, onAddToCart: handleAddToCart, onToggleUntuck: handleToggleUntuck, onSignOut: handleSignOut }),
44152
44456
  vtoError ? /* @__PURE__ */ jsx$1(Snackbar, { messageKey: "fitting_room.vto_error", onDismiss: clearVtoError }) : null
44153
44457
  ] }) });
44154
44458
  }
@@ -44338,7 +44642,7 @@ function GetAppOverlay({
44338
44642
  openOverlay(OverlayName.SIGN_IN, {
44339
44643
  returnToOverlay
44340
44644
  });
44341
- }, [returnToOverlay]);
44645
+ }, [openOverlay, returnToOverlay]);
44342
44646
  const handleGetAppAppleClick = reactExports.useCallback(() => {
44343
44647
  const url = getStaticData().config.links.appAppleStoreUrl;
44344
44648
  window.open(url, "_blank");
@@ -44405,12 +44709,12 @@ function LandingOverlay({
44405
44709
  openOverlay(OverlayName.GET_APP, {
44406
44710
  returnToOverlay
44407
44711
  });
44408
- }, [returnToOverlay]);
44712
+ }, [openOverlay, returnToOverlay]);
44409
44713
  const handleSignInClick = reactExports.useCallback(() => {
44410
44714
  openOverlay(OverlayName.SIGN_IN, {
44411
44715
  returnToOverlay
44412
44716
  });
44413
- }, [returnToOverlay]);
44717
+ }, [openOverlay, returnToOverlay]);
44414
44718
  return /* @__PURE__ */ jsxs(ContentModal, { onRequestClose: closeOverlay, title: /* @__PURE__ */ jsx$1(TextT, { variant: "brand", css: css2.titleText, t: "try_it_on" }), children: [
44415
44719
  /* @__PURE__ */ jsx$1("div", { css: css2.headerText, children: /* @__PURE__ */ jsx$1(TextT, { variant: "brand", css: css2.headerText, t: t("landing.header") }) }),
44416
44720
  /* @__PURE__ */ jsx$1("div", { css: css2.description, children: /* @__PURE__ */ jsx$1(TextT, { variant: "base", t: t("landing.description") }) }),
@@ -44550,12 +44854,12 @@ function SignInOverlay({
44550
44854
  openOverlay(OverlayName.FORGOT_PASSWORD, {
44551
44855
  returnToOverlay
44552
44856
  });
44553
- }, [returnToOverlay]);
44857
+ }, [openOverlay, returnToOverlay]);
44554
44858
  const handleGetAppClick = reactExports.useCallback(() => {
44555
44859
  openOverlay(OverlayName.GET_APP, {
44556
44860
  returnToOverlay
44557
44861
  });
44558
- }, [returnToOverlay]);
44862
+ }, [openOverlay, returnToOverlay]);
44559
44863
  return /* @__PURE__ */ jsx$1(ContentModal, { onRequestClose: closeOverlay, title: /* @__PURE__ */ jsx$1(TfrTitle, {}), children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, css: css2.form, children: [
44560
44864
  /* @__PURE__ */ jsx$1("div", { css: css2.emailContainer, children: /* @__PURE__ */ jsx$1("input", { name: "email", type: "email", placeholder: t("sign-in.email"), required: true, css: /* @__PURE__ */ css$1({
44561
44865
  ...css2.input,
@@ -44942,7 +45246,9 @@ function QuickViewOverlay() {
44942
45246
  const {
44943
45247
  color: selectedColor
44944
45248
  } = await currentProduct.getSelectedOptions();
44945
- const styleCategoryLabel = storeProduct.style.style_category_label || null;
45249
+ const styleCategoryIndex = await loadStyleCategoryIndex();
45250
+ const styleCategoryRecord = styleCategoryIndex.byName(storeProduct.style.style_category_name);
45251
+ const styleCategoryLabel = styleCategoryRecord?.label_singular ?? styleCategoryRecord?.label ?? null;
44946
45252
  const sizeRecommendationRecord = storeProduct.sizeFitRecommendation;
44947
45253
  {
44948
45254
  const recommendedSizeId = sizeRecommendationRecord.recommended_size.id || null;
@@ -45115,7 +45421,7 @@ function QuickViewOverlay() {
45115
45421
  error
45116
45422
  });
45117
45423
  });
45118
- }, [closeOverlay, openOverlay]);
45424
+ }, [closeOverlay]);
45119
45425
  const handleAddToCartClick = reactExports.useCallback(async () => {
45120
45426
  try {
45121
45427
  if (!selectedSizeLabel) {
@@ -45137,7 +45443,7 @@ function QuickViewOverlay() {
45137
45443
  error
45138
45444
  });
45139
45445
  }
45140
- }, [selectedColorLabel, selectedSizeLabel]);
45446
+ }, [closeOverlay, selectedColorLabel, selectedSizeLabel]);
45141
45447
  if (vtoProductData === false) {
45142
45448
  return /* @__PURE__ */ jsx$1(SidecarModalFrame, { onRequestClose: closeOverlay, children: /* @__PURE__ */ jsx$1(NoFitLayout, { onClose: closeOverlay, onSignOut: handleSignOutClick }) });
45143
45449
  }
@@ -45602,7 +45908,9 @@ function DesktopLayout({
45602
45908
  },
45603
45909
  productNameContainer: {},
45604
45910
  productNameText: {
45605
- fontSize: "32px"
45911
+ fontFamily: "'Inter', sans-serif",
45912
+ fontSize: "32px",
45913
+ fontWeight: 300
45606
45914
  },
45607
45915
  priceContainer: {
45608
45916
  marginTop: "8px"
@@ -45636,17 +45944,27 @@ function DesktopLayout({
45636
45944
  marginTop: "8px",
45637
45945
  lineHeight: "normal"
45638
45946
  },
45639
- itemFitText: {},
45947
+ itemFitText: {
45948
+ fontFamily: "'Inter', sans-serif",
45949
+ fontWeight: 300
45950
+ },
45640
45951
  selectSizeLabelContainer: {
45641
45952
  lineHeight: "normal"
45642
45953
  },
45643
- selectSizeLabelText: {},
45954
+ selectSizeLabelText: {
45955
+ fontFamily: "'Inter', sans-serif",
45956
+ fontWeight: 300
45957
+ },
45644
45958
  sizeSelectorContainer: {
45645
45959
  marginTop: "24px"
45646
45960
  },
45647
45961
  itemFitDetailsContainer: {
45648
45962
  marginTop: "24px",
45649
- width: "100%"
45963
+ width: "100%",
45964
+ // Cascades into <ItemFitDetails>; the bold recommended-size line above
45965
+ // and the size-selector buttons stay at their own weights.
45966
+ fontFamily: "'Inter', sans-serif",
45967
+ fontWeight: 300
45650
45968
  },
45651
45969
  fitChartContainer: {
45652
45970
  marginTop: "16px"
@@ -45851,7 +46169,7 @@ function Avatar({
45851
46169
  return () => {
45852
46170
  window.removeEventListener("resize", refreshLayoutData);
45853
46171
  };
45854
- }, [isMobileLayout]);
46172
+ }, [isMobileLayout, setModalStyle]);
45855
46173
  const isReady = !!frameUrls && selectedFrameIndex != null;
45856
46174
  return /* @__PURE__ */ jsxs("div", { css: css2.topContainer, style: layoutData.topContainerStyle, children: [
45857
46175
  /* @__PURE__ */ jsx$1(AvatarFrameViewer, { frameUrls, selectedFrameIndex, setSelectedFrameIndex, imageContainerStyle: layoutData.imageContainerStyle, imageStyle: layoutData.imageStyle, loadingT: "quick-view.avatar_loading" }),
@@ -45880,10 +46198,14 @@ function ProductSummaryRow({
45880
46198
  },
45881
46199
  labelContainer: {},
45882
46200
  labelText: {
46201
+ fontFamily: "'Times New Roman', serif",
46202
+ fontSize: "16px",
45883
46203
  color: "#1A1A1A"
45884
46204
  },
45885
46205
  nameContainer: {},
45886
46206
  nameText: {
46207
+ fontFamily: "'Times New Roman', serif",
46208
+ fontSize: "16px",
45887
46209
  color: "#9F9F9F"
45888
46210
  }
45889
46211
  }));
@@ -45892,35 +46214,6 @@ function ProductSummaryRow({
45892
46214
  /* @__PURE__ */ jsx$1("div", { css: css2.nameContainer, children: /* @__PURE__ */ jsx$1(Text, { variant: "brand", css: css2.nameText, children: loadedProductData.productName }) })
45893
46215
  ] });
45894
46216
  }
45895
- function ColorSelector({
45896
- availableColorLabels,
45897
- selectedColorLabel,
45898
- onChangeColor
45899
- }) {
45900
- const css2 = useCss((theme) => ({
45901
- colorContainer: {},
45902
- colorLabelText: {
45903
- fontSize: "12px"
45904
- },
45905
- colorSelect: {
45906
- border: "none",
45907
- color: theme.color_fg_text,
45908
- fontFamily: theme.font_family,
45909
- fontSize: "12px"
45910
- }
45911
- }));
45912
- const handleColorSelectChange = reactExports.useCallback((e) => {
45913
- const newColorLabel = e.target.value || null;
45914
- onChangeColor(newColorLabel);
45915
- }, []);
45916
- if (availableColorLabels.length < 2) {
45917
- return null;
45918
- }
45919
- return /* @__PURE__ */ jsx$1("div", { css: css2.colorContainer, children: /* @__PURE__ */ jsxs("label", { children: [
45920
- /* @__PURE__ */ jsx$1(TextT, { variant: "base", css: css2.colorLabelText, t: "quick-view.color_label" }),
45921
- /* @__PURE__ */ jsx$1("select", { value: selectedColorLabel ?? "", onChange: handleColorSelectChange, css: css2.colorSelect, children: availableColorLabels.map((colorLabel) => /* @__PURE__ */ jsx$1("option", { value: colorLabel, children: colorLabel }, colorLabel)) })
45922
- ] }) });
45923
- }
45924
46217
  function RecommendedSizeText({
45925
46218
  loadedProductData,
45926
46219
  textCss
@@ -46501,9 +46794,9 @@ const SHARED_CONFIG = {
46501
46794
  appGooglePlayUrl: "https://play.google.com/store/apps/details?id=com.thefittingroom.marketplace"
46502
46795
  },
46503
46796
  build: {
46504
- version: `${"5.0.24"}`,
46505
- commitHash: `${"931e0b2"}`,
46506
- date: `${"2026-05-19T21:17:10.057Z"}`
46797
+ version: `${"5.0.26"}`,
46798
+ commitHash: `${"357d81c"}`,
46799
+ date: `${"2026-05-23T18:08:09.620Z"}`
46507
46800
  }
46508
46801
  };
46509
46802
  const CONFIGS = {
@@ -46627,7 +46920,7 @@ const CONFIGS = {
46627
46920
  const getConfig = (envName) => {
46628
46921
  return CONFIGS[envName];
46629
46922
  };
46630
- const css = "\n@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');\n/* Inter — the SDK's own typeface, matching the Figma designs. Loaded here so\n overlay text renders in Inter regardless of the host page's fonts. */\nbody.tfr-modal-open {\n overflow: hidden;\n position: fixed;\n width: 100%;\n}\n";
46923
+ const css = "\n@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap');\n/* Inter — the SDK's own typeface, matching the Figma designs. Loaded here so\n overlay text renders in Inter regardless of the host page's fonts. The\n variable-axis URL serves every integer weight from 100 to 900 in one file\n so any `font-weight: <n>` value renders accurately without falling back to\n the nearest pre-loaded weight. */\nbody.tfr-modal-open {\n overflow: hidden;\n position: fixed;\n width: 100%;\n}\n";
46631
46924
  class TfrWidgetElement extends HTMLElement {
46632
46925
  connectedCallback() {
46633
46926
  const attributes = this.getAttributeNames().reduce((attrs, name2) => {
@@ -46650,7 +46943,8 @@ async function init(initParams) {
46650
46943
  debug,
46651
46944
  productLookup,
46652
46945
  getOverlayTopOffset,
46653
- addToCart
46946
+ addToCart,
46947
+ testHooks
46654
46948
  } = initParams;
46655
46949
  if (!brandId || typeof brandId !== "number" || isNaN(brandId) || brandId <= 0) {
46656
46950
  throw new Error(`Invalid brandId "${brandId}"`);
@@ -46676,7 +46970,8 @@ async function init(initParams) {
46676
46970
  config,
46677
46971
  productLookup: productLookup ?? null,
46678
46972
  getOverlayTopOffset: getOverlayTopOffset ?? null,
46679
- addToCart: addToCart ?? null
46973
+ addToCart: addToCart ?? null,
46974
+ testHooks
46680
46975
  });
46681
46976
  _init$7();
46682
46977
  _init$5();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thefittingroom/shop-ui",
3
- "version": "5.0.24",
3
+ "version": "5.0.26",
4
4
  "description": "the fitting room UI library",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,8 +22,12 @@
22
22
  "promote-latest": "sh scripts/promote-latest.sh",
23
23
  "lint": "eslint .",
24
24
  "lint:fix": "eslint . --fix",
25
- "format": "prettier --write src",
26
- "format:check": "prettier --check src"
25
+ "format": "prettier --write src tests playwright.config.ts",
26
+ "format:check": "prettier --check src tests playwright.config.ts",
27
+ "test": "npm run test:e2e",
28
+ "test:e2e": "playwright test",
29
+ "test:e2e:debug": "PWDEBUG=1 playwright test",
30
+ "test:e2e:ui": "playwright test --ui"
27
31
  },
28
32
  "engines": {
29
33
  "node": ">=22"
@@ -33,6 +37,7 @@
33
37
  },
34
38
  "devDependencies": {
35
39
  "@eslint/js": "^9.39.4",
40
+ "@playwright/test": "^1.60.0",
36
41
  "@types/react": "^19.2.7",
37
42
  "@types/react-dom": "^19.2.3",
38
43
  "@types/react-modal": "^3.16.3",