@snacky/ui 0.12.0 → 0.13.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 CHANGED
@@ -4,6 +4,39 @@ How `@snacky/ui` got to its current state. The README documents what the
4
4
  package *is*; this documents how it got there, including the mistakes, so the
5
5
  verification claims in the README can be taken at face value.
6
6
 
7
+ ## ProductGroupSection, and a mouse-draggable product row (0.13.0)
8
+
9
+ `ProductGroupSection` is a titled group of list `ProductCard`s with
10
+ `layout="horizontal" | "banner" | "grid"`. It packages three variants of Figma's
11
+ Section set (`Group-Products-Horizontal`, `-Banner`, `-vertical`), which between
12
+ them have 8 instances across the Home and product detail screens.
13
+
14
+ The reason it exists is that `Section` is a shell: a title, an optional see-more
15
+ chevron, and `children`. Those three variants were never props on it, so every
16
+ use meant rebuilding the same things by hand, and a design agent working from the
17
+ package had to improvise them: the trailing "See other products" card (which
18
+ Figma also draws by hand, twice), a row that runs out to the screen's right edge,
19
+ and for the banner variant, zero side padding plus a full-bleed banner with the
20
+ row laid over it from x160. The component carries all of that:
21
+
22
+ - `onSeeMore` shows the header chevron and, in the two scrolling layouts, the
23
+ trailing card (`seeMoreLabel` defaults to "See other products").
24
+ - `banner` is required for `layout="banner"`, normally a `SquareBanner`. The row
25
+ spans the full width with its first card at x160, so scrolling carries the cards
26
+ over the banner while the banner stays put.
27
+ - Rows scroll by touch and trackpad natively, and by mouse drag. The pointer is
28
+ captured only after a 4px move, so a tap on a card's cart button still lands.
29
+
30
+ Heights match the Figma nodes: 372 horizontal, 396 banner, 1278 for a grid of
31
+ eight cards. `Section` itself is unchanged; the order-screen Section variants have
32
+ no instances in Figma and stay compositions inside it.
33
+
34
+ compose-ui ships the same as `SnackyProductGroupSection` with
35
+ `ProductGroupLayout.Horizontal | Banner | Grid` in `compose-v2.3.0`, where the
36
+ banner is an image slot like the rest of the Banner family. `SnackySection`'s
37
+ header is now a shared internal `SnackySectionHeader`; its public API is
38
+ unchanged. Additive on both sides.
39
+
7
40
  ## The solid address icon (0.12.0)
8
41
 
9
42
  `SnackyIcons.solid.address` is new: the filled map marker, added to Figma's
@@ -23,9 +56,10 @@ compose-ui ships the same icon as `SnackyIcons.Solid.Address` in
23
56
  `SnackyTypographyToken.toTextStyle(color, fontFamily)`. compose-ui's type tokens
24
57
  are raw values rather than `TextStyle`s (the package ships no font), and nothing
25
58
  turned one into the other, so every component built the style by hand and 58 of
26
- the docs site's Kotlin samples could not compile: 24 called a `toTextStyle()`
27
- that did not exist, and the rest used flat names like
28
- `SnackyTypography.smallRegular` that never did either. Those samples now use
59
+ the docs site's Kotlin samples were broken or incomplete: 24 called a
60
+ `toTextStyle()` that only existed as a helper the Foundations page told you to
61
+ paste in yourself, and the rest used flat names like
62
+ `SnackyTypography.smallRegular` that never existed at all. Those samples now use
29
63
  `SnackyTypography.Small.regular.toTextStyle()`. The React samples had the same
30
64
  flat-name problem (`typography.smallRegular`, 43 places) against an export that
31
65
  is nested (`typography.small.regular`), and are fixed the same way. Additive on
package/README.md CHANGED
@@ -121,7 +121,7 @@ placeholder artwork rather than a real icon prop. Apply your own
121
121
 
122
122
  ## What's here
123
123
 
124
- All 25 documented components, matching the real Figma-sourced spec values
124
+ All 26 documented components, matching the real Figma-sourced spec values
125
125
  (padding, colors, radius, states - all sourced directly from the design
126
126
  tokens, not eyeballed):
127
127
 
@@ -130,7 +130,8 @@ OtpField, CopyField, ChatInput, AddressResult), Chips (ProductChip/FilterChip),
130
130
  RadioOption, Checkbox, Toggle, NavBar, TabRow, Header, PageIndicator, Banner
131
131
  family, Badge family,
132
132
  Callout, List (OrderListItem/NotificationListItem), Accordion, BottomSheet
133
- (the shared Modal shell), Section (the shared content-block shell), Avatar,
133
+ (the shared Modal shell), Section (the shared content-block shell),
134
+ ProductGroupSection (titled product rows and grids), Avatar,
134
135
  Illustration, ProductImage, ProductCard.
135
136
 
136
137
  Plus the icon set, as `SnackyIcons.outline.*` and `SnackyIcons.solid.*` (42
package/dist/index.cjs CHANGED
@@ -49,6 +49,7 @@ __export(index_exports, {
49
49
  PointBalanceBanner: () => PointBalanceBanner,
50
50
  ProductCard: () => ProductCard,
51
51
  ProductChip: () => ProductChip,
52
+ ProductGroupSection: () => ProductGroupSection,
52
53
  ProductImage: () => ProductImage,
53
54
  RadioOption: () => RadioOption,
54
55
  SearchField: () => SearchField,
@@ -1067,18 +1068,102 @@ function Section({ title, onAction, children, className }) {
1067
1068
  ] });
1068
1069
  }
1069
1070
 
1070
- // src/components/Stepper/Stepper.tsx
1071
+ // src/components/ProductGroupSection/ProductGroupSection.tsx
1072
+ var import_react12 = require("react");
1071
1073
  var import_jsx_runtime28 = require("react/jsx-runtime");
1074
+ function ProductGroupSection({
1075
+ title,
1076
+ layout,
1077
+ children,
1078
+ onSeeMore,
1079
+ seeMoreLabel = "See other products",
1080
+ banner,
1081
+ className
1082
+ }) {
1083
+ const drag = useDragScroll();
1084
+ const seeMore = onSeeMore && layout !== "grid" && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "snacky-product-group__see-more", children: [
1085
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(IconButton, { size: "small", icon: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(angleSmallRight, {}), onClick: onSeeMore, ariaLabel: seeMoreLabel }),
1086
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "snacky-product-group__see-more-label", children: seeMoreLabel })
1087
+ ] });
1088
+ return /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1089
+ Section,
1090
+ {
1091
+ title,
1092
+ onAction: onSeeMore,
1093
+ className: cx("snacky-product-group", `snacky-product-group--${layout}`, className),
1094
+ children: [
1095
+ layout === "grid" && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "snacky-product-group__grid", children }),
1096
+ layout === "horizontal" && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "snacky-product-group__row", ...drag, children: [
1097
+ children,
1098
+ seeMore
1099
+ ] }),
1100
+ layout === "banner" && /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "snacky-product-group__stage", children: [
1101
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "snacky-product-group__banner", children: banner }),
1102
+ /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("div", { className: "snacky-product-group__row", ...drag, children: [
1103
+ children,
1104
+ seeMore
1105
+ ] })
1106
+ ] })
1107
+ ]
1108
+ }
1109
+ );
1110
+ }
1111
+ function useDragScroll() {
1112
+ const state = (0, import_react12.useRef)(null);
1113
+ const justDragged = (0, import_react12.useRef)(false);
1114
+ return {
1115
+ onPointerDown(e) {
1116
+ if (e.pointerType !== "mouse") return;
1117
+ state.current = { x: e.clientX, left: e.currentTarget.scrollLeft, moved: false, id: e.pointerId };
1118
+ },
1119
+ onPointerMove(e) {
1120
+ const d = state.current;
1121
+ if (!d) return;
1122
+ const dx = e.clientX - d.x;
1123
+ if (!d.moved && Math.abs(dx) > 4) {
1124
+ d.moved = true;
1125
+ e.currentTarget.setPointerCapture(d.id);
1126
+ e.currentTarget.classList.add("snacky-product-group__row--dragging");
1127
+ }
1128
+ if (d.moved) e.currentTarget.scrollLeft = d.left - dx;
1129
+ },
1130
+ onPointerUp(e) {
1131
+ if (state.current?.moved) {
1132
+ e.currentTarget.classList.remove("snacky-product-group__row--dragging");
1133
+ justDragged.current = true;
1134
+ setTimeout(() => {
1135
+ justDragged.current = false;
1136
+ }, 0);
1137
+ }
1138
+ state.current = null;
1139
+ },
1140
+ onPointerCancel() {
1141
+ state.current = null;
1142
+ },
1143
+ onClickCapture(e) {
1144
+ if (justDragged.current) {
1145
+ e.stopPropagation();
1146
+ e.preventDefault();
1147
+ }
1148
+ },
1149
+ onDragStart(e) {
1150
+ e.preventDefault();
1151
+ }
1152
+ };
1153
+ }
1154
+
1155
+ // src/components/Stepper/Stepper.tsx
1156
+ var import_jsx_runtime29 = require("react/jsx-runtime");
1072
1157
  function Stepper({ steps, className }) {
1073
- return /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("ol", { className: cx("snacky-stepper", className), children: steps.map((step, i) => /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)(
1158
+ return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ol", { className: cx("snacky-stepper", className), children: steps.map((step, i) => /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1074
1159
  "li",
1075
1160
  {
1076
1161
  className: cx("snacky-stepper__step", i === steps.length - 1 && "snacky-stepper__step--last"),
1077
1162
  children: [
1078
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: cx("snacky-stepper__dot", `snacky-stepper__dot--${step.state}`), children: step.state === "cancelled" && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(close, { width: 12, height: 12 }) }),
1079
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "snacky-stepper__body", children: [
1080
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: cx("snacky-stepper__label", `snacky-stepper__label--${step.state}`), children: step.label }),
1081
- step.timestamp && /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: "snacky-stepper__time", children: step.timestamp })
1163
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: cx("snacky-stepper__dot", `snacky-stepper__dot--${step.state}`), children: step.state === "cancelled" && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(close, { width: 12, height: 12 }) }),
1164
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { className: "snacky-stepper__body", children: [
1165
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: cx("snacky-stepper__label", `snacky-stepper__label--${step.state}`), children: step.label }),
1166
+ step.timestamp && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "snacky-stepper__time", children: step.timestamp })
1082
1167
  ] })
1083
1168
  ]
1084
1169
  },
@@ -1087,7 +1172,7 @@ function Stepper({ steps, className }) {
1087
1172
  }
1088
1173
 
1089
1174
  // src/components/Calendar/Calendar.tsx
1090
- var import_jsx_runtime29 = require("react/jsx-runtime");
1175
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1091
1176
  var WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1092
1177
  var MONTHS = [
1093
1178
  "January",
@@ -1134,39 +1219,39 @@ function Calendar({
1134
1219
  const [lo, hi] = range ? [range[0], range[1]].sort((a, b) => a.getTime() - b.getTime()) : [null, null];
1135
1220
  const isEnd = (d) => single && sameDay(d, single) || lo && hi && (sameDay(d, lo) || sameDay(d, hi));
1136
1221
  const isInRange = (d) => lo && hi && d > lo && d < hi;
1137
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: cx("snacky-calendar", className), children: [
1138
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "snacky-calendar__header", children: [
1139
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1222
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: cx("snacky-calendar", className), children: [
1223
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "snacky-calendar__header", children: [
1224
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1140
1225
  "button",
1141
1226
  {
1142
1227
  type: "button",
1143
1228
  className: "snacky-calendar__nav",
1144
1229
  onClick: onPrevMonth,
1145
1230
  "aria-label": "Previous month",
1146
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(back, { width: 24, height: 24 })
1231
+ children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(back, { width: 24, height: 24 })
1147
1232
  }
1148
1233
  ),
1149
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("span", { className: "snacky-calendar__label", children: [
1234
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("span", { className: "snacky-calendar__label", children: [
1150
1235
  MONTHS[month.getMonth()],
1151
1236
  " ",
1152
1237
  month.getFullYear()
1153
1238
  ] }),
1154
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1239
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1155
1240
  "button",
1156
1241
  {
1157
1242
  type: "button",
1158
1243
  className: "snacky-calendar__nav snacky-calendar__nav--next",
1159
1244
  onClick: onNextMonth,
1160
1245
  "aria-label": "Next month",
1161
- children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(back, { width: 24, height: 24 })
1246
+ children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(back, { width: 24, height: 24 })
1162
1247
  }
1163
1248
  )
1164
1249
  ] }),
1165
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "snacky-calendar__grid", children: [
1166
- WEEKDAYS.map((d) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "snacky-calendar__weekday", children: d }, d)),
1250
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "snacky-calendar__grid", children: [
1251
+ WEEKDAYS.map((d) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "snacky-calendar__weekday", children: d }, d)),
1167
1252
  days.map((d, i) => {
1168
1253
  const outside = d.getMonth() !== month.getMonth();
1169
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1254
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
1170
1255
  "button",
1171
1256
  {
1172
1257
  type: "button",
@@ -1180,26 +1265,26 @@ function Calendar({
1180
1265
  disabled: outside,
1181
1266
  "aria-current": isEnd(d) ? "date" : void 0,
1182
1267
  children: [
1183
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "snacky-calendar__day-number", children: d.getDate() }),
1184
- marked.some((m) => sameDay(m, d)) && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "snacky-calendar__marker" })
1268
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "snacky-calendar__day-number", children: d.getDate() }),
1269
+ marked.some((m) => sameDay(m, d)) && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { className: "snacky-calendar__marker" })
1185
1270
  ]
1186
1271
  },
1187
1272
  i
1188
1273
  );
1189
1274
  })
1190
1275
  ] }),
1191
- onAction && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(Button, { onClick: onAction, style: { width: "100%" }, children: actionLabel })
1276
+ onAction && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(Button, { onClick: onAction, style: { width: "100%" }, children: actionLabel })
1192
1277
  ] });
1193
1278
  }
1194
1279
 
1195
1280
  // src/components/Avatar/Avatar.tsx
1196
- var import_jsx_runtime30 = require("react/jsx-runtime");
1281
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1197
1282
  function Avatar({ src, alt, size = "md", className }) {
1198
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("img", { src, alt, className: cx("snacky-avatar", `snacky-avatar--${size}`, className) });
1283
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("img", { src, alt, className: cx("snacky-avatar", `snacky-avatar--${size}`, className) });
1199
1284
  }
1200
1285
 
1201
1286
  // src/components/Illustration/Illustration.tsx
1202
- var import_jsx_runtime31 = require("react/jsx-runtime");
1287
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1203
1288
  var SIZE = {
1204
1289
  empty: { width: 268, height: 200 },
1205
1290
  createAccount: { width: 360, height: 240 },
@@ -1209,54 +1294,54 @@ var SIZE = {
1209
1294
  };
1210
1295
  function Illustration({ variant, src, alt, className }) {
1211
1296
  const { width, height } = SIZE[variant];
1212
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("img", { src, alt, width, height, className: cx("snacky-illustration", className) });
1297
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("img", { src, alt, width, height, className: cx("snacky-illustration", className) });
1213
1298
  }
1214
1299
 
1215
1300
  // src/components/ProductImage/ProductImage.tsx
1216
- var import_jsx_runtime32 = require("react/jsx-runtime");
1301
+ var import_jsx_runtime33 = require("react/jsx-runtime");
1217
1302
  function ProductImage({ src, alt, usage, sold = false, className }) {
1218
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: cx("snacky-product-image-wrap", `snacky-product-image-wrap--${usage}`, className), children: [
1219
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("img", { src, alt, className: cx("snacky-product-image", `snacky-product-image--${usage}`) }),
1220
- sold && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "snacky-product-image-sold-overlay", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "snacky-product-image-sold-label", children: "Sold Out" }) })
1303
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("span", { className: cx("snacky-product-image-wrap", `snacky-product-image-wrap--${usage}`, className), children: [
1304
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("img", { src, alt, className: cx("snacky-product-image", `snacky-product-image--${usage}`) }),
1305
+ sold && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "snacky-product-image-sold-overlay", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "snacky-product-image-sold-label", children: "Sold Out" }) })
1221
1306
  ] });
1222
1307
  }
1223
1308
 
1224
1309
  // src/components/ProductCard/ProductCard.tsx
1225
- var import_jsx_runtime33 = require("react/jsx-runtime");
1310
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1226
1311
  function ProductCard(props) {
1227
1312
  if (props.variant === "details") {
1228
1313
  const { productName: productName2, imageUrl: imageUrl2, price: price2, rating: rating2, originalPrice: originalPrice2, discountLabel: discountLabel2, ratingCount, favorited, onFavoriteClick, onShareClick, onChatClick, sold, ratingIcon: ratingIcon2, favoriteIcon, shareIcon, chatIcon, onClick: onClick2, className: className2 } = props;
1229
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: cx("snacky-product-card", "snacky-product-card--details", className2), children: [
1230
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { className: "snacky-product-card__image-block", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "snacky-product-card__image-wrap", onClick: onClick2, children: [
1231
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("img", { className: "snacky-product-card__image", src: imageUrl2, alt: productName2 }),
1232
- sold && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "snacky-product-card__sold-overlay", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(SoldOutBadge, {}) })
1314
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: cx("snacky-product-card", "snacky-product-card--details", className2), children: [
1315
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "snacky-product-card__image-block", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "snacky-product-card__image-wrap", onClick: onClick2, children: [
1316
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("img", { className: "snacky-product-card__image", src: imageUrl2, alt: productName2 }),
1317
+ sold && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "snacky-product-card__sold-overlay", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(SoldOutBadge, {}) })
1233
1318
  ] }) }),
1234
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "snacky-product-card__details-body", children: [
1235
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "snacky-product-card__name", children: productName2 }),
1236
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "snacky-product-card__price-row", children: [
1237
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "snacky-product-card__price", children: price2 }),
1238
- originalPrice2 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "snacky-product-card__original-price", children: originalPrice2 }),
1239
- discountLabel2 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(DiscountTag, { label: discountLabel2 })
1319
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "snacky-product-card__details-body", children: [
1320
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { className: "snacky-product-card__name", children: productName2 }),
1321
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "snacky-product-card__price-row", children: [
1322
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "snacky-product-card__price", children: price2 }),
1323
+ originalPrice2 && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "snacky-product-card__original-price", children: originalPrice2 }),
1324
+ discountLabel2 && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(DiscountTag, { label: discountLabel2 })
1240
1325
  ] }),
1241
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "snacky-product-card__footer-row", children: [
1242
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("span", { className: "snacky-product-card__rating", children: [
1243
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "snacky-product-card__rating-icon", children: ratingIcon2 ?? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(star, { width: 16, height: 16 }) }),
1326
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "snacky-product-card__footer-row", children: [
1327
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "snacky-product-card__rating", children: [
1328
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "snacky-product-card__rating-icon", children: ratingIcon2 ?? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(star, { width: 16, height: 16 }) }),
1244
1329
  rating2,
1245
1330
  " (",
1246
1331
  ratingCount,
1247
1332
  ")"
1248
1333
  ] }),
1249
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "snacky-product-card__actions snacky-product-card__actions--details", children: [
1250
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(IconButton, { variant: "secondary", icon: favoriteIcon ?? (favorited ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(heart2, { width: 20, height: 20 }) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(heart, { width: 20, height: 20 })), selected: favorited, onClick: onFavoriteClick, ariaLabel: "Favorite" }),
1251
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(IconButton, { variant: "secondary", icon: shareIcon ?? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(share, { width: 20, height: 20 }), onClick: onShareClick, ariaLabel: "Share" }),
1252
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(IconButton, { variant: "secondary", icon: chatIcon ?? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(chat, { width: 20, height: 20 }), onClick: onChatClick, ariaLabel: "Chat with seller" })
1334
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "snacky-product-card__actions snacky-product-card__actions--details", children: [
1335
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(IconButton, { variant: "secondary", icon: favoriteIcon ?? (favorited ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(heart2, { width: 20, height: 20 }) : /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(heart, { width: 20, height: 20 })), selected: favorited, onClick: onFavoriteClick, ariaLabel: "Favorite" }),
1336
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(IconButton, { variant: "secondary", icon: shareIcon ?? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(share, { width: 20, height: 20 }), onClick: onShareClick, ariaLabel: "Share" }),
1337
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(IconButton, { variant: "secondary", icon: chatIcon ?? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(chat, { width: 20, height: 20 }), onClick: onChatClick, ariaLabel: "Chat with seller" })
1253
1338
  ] })
1254
1339
  ] })
1255
1340
  ] })
1256
1341
  ] });
1257
1342
  }
1258
1343
  const { productName, imageUrl, price, rating, originalPrice, discountLabel, onAddToCart, cartIcon, ratingIcon, onClick, className } = props;
1259
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
1344
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
1260
1345
  "div",
1261
1346
  {
1262
1347
  className: cx("snacky-product-card", "snacky-product-card--list", className),
@@ -1265,25 +1350,25 @@ function ProductCard(props) {
1265
1350
  onClick,
1266
1351
  onKeyDown: (e) => (e.key === "Enter" || e.key === " ") && onClick?.(),
1267
1352
  children: [
1268
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "snacky-product-card__image-wrap", children: [
1269
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("img", { className: "snacky-product-card__image", src: imageUrl, alt: productName }),
1270
- discountLabel && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "snacky-product-card__discount", children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(DiscountTag, { label: discountLabel }) })
1353
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "snacky-product-card__image-wrap", children: [
1354
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("img", { className: "snacky-product-card__image", src: imageUrl, alt: productName }),
1355
+ discountLabel && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "snacky-product-card__discount", children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(DiscountTag, { label: discountLabel }) })
1271
1356
  ] }),
1272
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "snacky-product-card__name", children: productName }),
1273
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "snacky-product-card__price-row", children: [
1274
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "snacky-product-card__price", children: price }),
1275
- originalPrice && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "snacky-product-card__original-price", children: originalPrice })
1357
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("p", { className: "snacky-product-card__name", children: productName }),
1358
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "snacky-product-card__price-row", children: [
1359
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "snacky-product-card__price", children: price }),
1360
+ originalPrice && /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "snacky-product-card__original-price", children: originalPrice })
1276
1361
  ] }),
1277
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "snacky-product-card__footer-row", children: [
1278
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("span", { className: "snacky-product-card__rating", children: [
1279
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "snacky-product-card__rating-icon", children: ratingIcon ?? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(star, { width: 16, height: 16 }) }),
1362
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "snacky-product-card__footer-row", children: [
1363
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("span", { className: "snacky-product-card__rating", children: [
1364
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "snacky-product-card__rating-icon", children: ratingIcon ?? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(star, { width: 16, height: 16 }) }),
1280
1365
  rating
1281
1366
  ] }),
1282
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1367
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1283
1368
  IconButton,
1284
1369
  {
1285
1370
  variant: "primary",
1286
- icon: cartIcon ?? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(cartAdd, { width: 16, height: 16 }),
1371
+ icon: cartIcon ?? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(cartAdd, { width: 16, height: 16 }),
1287
1372
  onClick: (e) => {
1288
1373
  e.stopPropagation();
1289
1374
  onAddToCart();
@@ -1298,17 +1383,17 @@ function ProductCard(props) {
1298
1383
  }
1299
1384
 
1300
1385
  // src/components/ImagePlaceholder/ImagePlaceholder.tsx
1301
- var import_jsx_runtime34 = require("react/jsx-runtime");
1386
+ var import_jsx_runtime35 = require("react/jsx-runtime");
1302
1387
  function ImagePlaceholder({ width, height, className }) {
1303
1388
  const iconSize = Math.round(Math.min(width, height) * 0.32);
1304
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1389
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
1305
1390
  "div",
1306
1391
  {
1307
1392
  className: cx("snacky-image-placeholder", className),
1308
1393
  style: { width, height },
1309
1394
  role: "img",
1310
1395
  "aria-label": "No image",
1311
- children: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
1396
+ children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
1312
1397
  "svg",
1313
1398
  {
1314
1399
  width: iconSize,
@@ -1320,9 +1405,9 @@ function ImagePlaceholder({ width, height, className }) {
1320
1405
  strokeLinecap: "round",
1321
1406
  strokeLinejoin: "round",
1322
1407
  children: [
1323
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
1324
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("circle", { cx: "8.5", cy: "8.5", r: "1.5", fill: "currentColor", stroke: "none" }),
1325
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("polyline", { points: "21 15 16 10 5 21" })
1408
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
1409
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("circle", { cx: "8.5", cy: "8.5", r: "1.5", fill: "currentColor", stroke: "none" }),
1410
+ /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("polyline", { points: "21 15 16 10 5 21" })
1326
1411
  ]
1327
1412
  }
1328
1413
  )
@@ -1360,6 +1445,7 @@ function ImagePlaceholder({ width, height, className }) {
1360
1445
  PointBalanceBanner,
1361
1446
  ProductCard,
1362
1447
  ProductChip,
1448
+ ProductGroupSection,
1363
1449
  ProductImage,
1364
1450
  RadioOption,
1365
1451
  SearchField,