@snacky/ui 0.11.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,67 @@ 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
+
40
+ ## The solid address icon (0.12.0)
41
+
42
+ `SnackyIcons.solid.address` is new: the filled map marker, added to Figma's
43
+ `Icon-solid` set as `general/pin` (UIcons `fi-sr-marker`), 16x16, with its fill
44
+ bound to `icon/icon-brand`. The solid set is 13 icons now.
45
+
46
+ It ships as `address` rather than `pin` because a solid icon here takes the name
47
+ of its outline twin, and this is the same marker shape as the existing
48
+ `SnackyIcons.outline.address`. That is the rule the rest of the set already
49
+ follows (Figma's `riwayat` is `history`, `Fav` is `heart`), and it means an
50
+ active/inactive pair is always `outline.x` and `solid.x` with no lookup table.
51
+ It is 16px where its outline twin is 24; `heart` has the same kind of mismatch
52
+ (20 solid, 24 outline), and each icon still defaults to its own authored size.
53
+
54
+ compose-ui ships the same icon as `SnackyIcons.Solid.Address` in
55
+ `compose-v2.2.0`, plus one helper it was missing:
56
+ `SnackyTypographyToken.toTextStyle(color, fontFamily)`. compose-ui's type tokens
57
+ are raw values rather than `TextStyle`s (the package ships no font), and nothing
58
+ turned one into the other, so every component built the style by hand and 58 of
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
63
+ `SnackyTypography.Small.regular.toTextStyle()`. The React samples had the same
64
+ flat-name problem (`typography.smallRegular`, 43 places) against an export that
65
+ is nested (`typography.small.regular`), and are fixed the same way. Additive on
66
+ both sides.
67
+
7
68
  ## PageIndicator, and a semantic role for indicator dots (0.11.0)
8
69
 
9
70
  `PageIndicator` is the dot row under a carousel: 8x8 dots, a 28x8 pill for the
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,11 +130,12 @@ 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
137
- Outline, 12 Solid). Two things to know: they are **filled** shapes rather than
138
+ Outline, 13 Solid). Two things to know: they are **filled** shapes rather than
138
139
  stroked paths, so there is no `strokeWidth` to set, and the set is **not
139
140
  uniform** - each icon is authored at 16, 20 or 24px and defaults to its own
140
141
  natural size, so pass `width`/`height` if you need them to match.
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,
@@ -705,6 +706,7 @@ function TabRow({ tabs, selected, onSelect, className }) {
705
706
  var solid_exports = {};
706
707
  __export(solid_exports, {
707
708
  account: () => account2,
709
+ address: () => address2,
708
710
  angleSmallRight: () => angleSmallRight,
709
711
  balance: () => balance2,
710
712
  bell: () => bell2,
@@ -736,6 +738,9 @@ var account2 = icon2(20, [
736
738
  ["M10 10C12.7614 10 15 7.76142 15 5C15 2.23858 12.7614 0 10 0C7.23858 0 5 2.23858 5 5C5 7.76142 7.23858 10 10 10Z", "nonzero"],
737
739
  ["M10 11.6659C5.85977 11.6705 2.50461 15.0257 2.5 19.1659C2.5 19.6261 2.87309 19.9992 3.33332 19.9992H16.6666C17.1269 19.9992 17.5 19.6261 17.5 19.1659C17.4954 15.0257 14.1402 11.6705 10 11.6659Z", "nonzero"]
738
740
  ]);
741
+ var address2 = icon2(16, [
742
+ ["M8.00001 0.0283203C6.23597 0.030261 4.54473 0.731837 3.2973 1.97914C2.04987 3.22644 1.34813 4.91762 1.34601 6.68165C1.34601 8.39499 2.67268 11.0763 5.28934 14.651C5.60085 15.0777 6.00871 15.4249 6.47974 15.6643C6.95077 15.9036 7.47166 16.0283 8.00001 16.0283C8.52836 16.0283 9.04924 15.9036 9.52027 15.6643C9.9913 15.4249 10.3992 15.0777 10.7107 14.651C13.3273 11.0763 14.654 8.39499 14.654 6.68165C14.6519 4.91762 13.9501 3.22644 12.7027 1.97914C11.4553 0.731837 9.76405 0.030261 8.00001 0.0283203V0.0283203ZM8.00001 9.33365C7.47259 9.33365 6.95702 9.17726 6.51849 8.88424C6.07996 8.59122 5.73816 8.17475 5.53633 7.68748C5.3345 7.20021 5.28169 6.66403 5.38458 6.14675C5.48747 5.62946 5.74145 5.15431 6.11439 4.78137C6.48733 4.40843 6.96248 4.15445 7.47977 4.05156C7.99705 3.94867 8.53323 4.00148 9.0205 4.20331C9.50777 4.40514 9.92424 4.74694 10.2173 5.18547C10.5103 5.624 10.6667 6.13957 10.6667 6.66699C10.6667 7.37423 10.3857 8.05251 9.88563 8.55261C9.38553 9.0527 8.70725 9.33365 8.00001 9.33365Z", "nonzero"]
743
+ ]);
739
744
  var angleSmallRight = icon2(24, [
740
745
  ["M15.75 9.525L11.164 4.939C10.8827 4.65774 10.5011 4.49978 10.1032 4.49988C9.70534 4.49997 9.32381 4.65811 9.04255 4.9395C8.76129 5.2209 8.60333 5.6025 8.60342 6.00036C8.60352 6.39822 8.76166 6.77974 9.04305 7.061L13.629 11.646C13.6756 11.6925 13.7125 11.7476 13.7378 11.8084C13.763 11.8691 13.7759 11.9342 13.7759 12C13.7759 12.0658 13.763 12.1309 13.7378 12.1916C13.7125 12.2524 13.6756 12.3076 13.629 12.354L9.04305 16.939C8.76166 17.2203 8.60352 17.6018 8.60342 17.9997C8.60333 18.3975 8.76129 18.7791 9.04255 19.0605C9.32381 19.3419 9.70534 19.5 10.1032 19.5001C10.5011 19.5002 10.8827 19.3423 11.164 19.061L15.75 14.475C16.4053 13.818 16.7732 12.9279 16.7732 12C16.7732 11.0721 16.4053 10.182 15.75 9.525Z", "nonzero"]
741
746
  ]);
@@ -1063,18 +1068,102 @@ function Section({ title, onAction, children, className }) {
1063
1068
  ] });
1064
1069
  }
1065
1070
 
1066
- // src/components/Stepper/Stepper.tsx
1071
+ // src/components/ProductGroupSection/ProductGroupSection.tsx
1072
+ var import_react12 = require("react");
1067
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");
1068
1157
  function Stepper({ steps, className }) {
1069
- 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)(
1070
1159
  "li",
1071
1160
  {
1072
1161
  className: cx("snacky-stepper__step", i === steps.length - 1 && "snacky-stepper__step--last"),
1073
1162
  children: [
1074
- /* @__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 }) }),
1075
- /* @__PURE__ */ (0, import_jsx_runtime28.jsxs)("span", { className: "snacky-stepper__body", children: [
1076
- /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("span", { className: cx("snacky-stepper__label", `snacky-stepper__label--${step.state}`), children: step.label }),
1077
- 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 })
1078
1167
  ] })
1079
1168
  ]
1080
1169
  },
@@ -1083,7 +1172,7 @@ function Stepper({ steps, className }) {
1083
1172
  }
1084
1173
 
1085
1174
  // src/components/Calendar/Calendar.tsx
1086
- var import_jsx_runtime29 = require("react/jsx-runtime");
1175
+ var import_jsx_runtime30 = require("react/jsx-runtime");
1087
1176
  var WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1088
1177
  var MONTHS = [
1089
1178
  "January",
@@ -1130,39 +1219,39 @@ function Calendar({
1130
1219
  const [lo, hi] = range ? [range[0], range[1]].sort((a, b) => a.getTime() - b.getTime()) : [null, null];
1131
1220
  const isEnd = (d) => single && sameDay(d, single) || lo && hi && (sameDay(d, lo) || sameDay(d, hi));
1132
1221
  const isInRange = (d) => lo && hi && d > lo && d < hi;
1133
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: cx("snacky-calendar", className), children: [
1134
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "snacky-calendar__header", children: [
1135
- /* @__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)(
1136
1225
  "button",
1137
1226
  {
1138
1227
  type: "button",
1139
1228
  className: "snacky-calendar__nav",
1140
1229
  onClick: onPrevMonth,
1141
1230
  "aria-label": "Previous month",
1142
- 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 })
1143
1232
  }
1144
1233
  ),
1145
- /* @__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: [
1146
1235
  MONTHS[month.getMonth()],
1147
1236
  " ",
1148
1237
  month.getFullYear()
1149
1238
  ] }),
1150
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
1239
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
1151
1240
  "button",
1152
1241
  {
1153
1242
  type: "button",
1154
1243
  className: "snacky-calendar__nav snacky-calendar__nav--next",
1155
1244
  onClick: onNextMonth,
1156
1245
  "aria-label": "Next month",
1157
- 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 })
1158
1247
  }
1159
1248
  )
1160
1249
  ] }),
1161
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "snacky-calendar__grid", children: [
1162
- 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)),
1163
1252
  days.map((d, i) => {
1164
1253
  const outside = d.getMonth() !== month.getMonth();
1165
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
1254
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
1166
1255
  "button",
1167
1256
  {
1168
1257
  type: "button",
@@ -1176,26 +1265,26 @@ function Calendar({
1176
1265
  disabled: outside,
1177
1266
  "aria-current": isEnd(d) ? "date" : void 0,
1178
1267
  children: [
1179
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "snacky-calendar__day-number", children: d.getDate() }),
1180
- 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" })
1181
1270
  ]
1182
1271
  },
1183
1272
  i
1184
1273
  );
1185
1274
  })
1186
1275
  ] }),
1187
- 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 })
1188
1277
  ] });
1189
1278
  }
1190
1279
 
1191
1280
  // src/components/Avatar/Avatar.tsx
1192
- var import_jsx_runtime30 = require("react/jsx-runtime");
1281
+ var import_jsx_runtime31 = require("react/jsx-runtime");
1193
1282
  function Avatar({ src, alt, size = "md", className }) {
1194
- 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) });
1195
1284
  }
1196
1285
 
1197
1286
  // src/components/Illustration/Illustration.tsx
1198
- var import_jsx_runtime31 = require("react/jsx-runtime");
1287
+ var import_jsx_runtime32 = require("react/jsx-runtime");
1199
1288
  var SIZE = {
1200
1289
  empty: { width: 268, height: 200 },
1201
1290
  createAccount: { width: 360, height: 240 },
@@ -1205,54 +1294,54 @@ var SIZE = {
1205
1294
  };
1206
1295
  function Illustration({ variant, src, alt, className }) {
1207
1296
  const { width, height } = SIZE[variant];
1208
- 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) });
1209
1298
  }
1210
1299
 
1211
1300
  // src/components/ProductImage/ProductImage.tsx
1212
- var import_jsx_runtime32 = require("react/jsx-runtime");
1301
+ var import_jsx_runtime33 = require("react/jsx-runtime");
1213
1302
  function ProductImage({ src, alt, usage, sold = false, className }) {
1214
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("span", { className: cx("snacky-product-image-wrap", `snacky-product-image-wrap--${usage}`, className), children: [
1215
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("img", { src, alt, className: cx("snacky-product-image", `snacky-product-image--${usage}`) }),
1216
- 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" }) })
1217
1306
  ] });
1218
1307
  }
1219
1308
 
1220
1309
  // src/components/ProductCard/ProductCard.tsx
1221
- var import_jsx_runtime33 = require("react/jsx-runtime");
1310
+ var import_jsx_runtime34 = require("react/jsx-runtime");
1222
1311
  function ProductCard(props) {
1223
1312
  if (props.variant === "details") {
1224
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;
1225
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: cx("snacky-product-card", "snacky-product-card--details", className2), children: [
1226
- /* @__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: [
1227
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("img", { className: "snacky-product-card__image", src: imageUrl2, alt: productName2 }),
1228
- 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, {}) })
1229
1318
  ] }) }),
1230
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "snacky-product-card__details-body", children: [
1231
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "snacky-product-card__name", children: productName2 }),
1232
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "snacky-product-card__price-row", children: [
1233
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "snacky-product-card__price", children: price2 }),
1234
- originalPrice2 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "snacky-product-card__original-price", children: originalPrice2 }),
1235
- 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 })
1236
1325
  ] }),
1237
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "snacky-product-card__footer-row", children: [
1238
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("span", { className: "snacky-product-card__rating", children: [
1239
- /* @__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 }) }),
1240
1329
  rating2,
1241
1330
  " (",
1242
1331
  ratingCount,
1243
1332
  ")"
1244
1333
  ] }),
1245
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "snacky-product-card__actions snacky-product-card__actions--details", children: [
1246
- /* @__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" }),
1247
- /* @__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" }),
1248
- /* @__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" })
1249
1338
  ] })
1250
1339
  ] })
1251
1340
  ] })
1252
1341
  ] });
1253
1342
  }
1254
1343
  const { productName, imageUrl, price, rating, originalPrice, discountLabel, onAddToCart, cartIcon, ratingIcon, onClick, className } = props;
1255
- return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
1344
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
1256
1345
  "div",
1257
1346
  {
1258
1347
  className: cx("snacky-product-card", "snacky-product-card--list", className),
@@ -1261,25 +1350,25 @@ function ProductCard(props) {
1261
1350
  onClick,
1262
1351
  onKeyDown: (e) => (e.key === "Enter" || e.key === " ") && onClick?.(),
1263
1352
  children: [
1264
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "snacky-product-card__image-wrap", children: [
1265
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("img", { className: "snacky-product-card__image", src: imageUrl, alt: productName }),
1266
- 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 }) })
1267
1356
  ] }),
1268
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { className: "snacky-product-card__name", children: productName }),
1269
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "snacky-product-card__price-row", children: [
1270
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "snacky-product-card__price", children: price }),
1271
- 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 })
1272
1361
  ] }),
1273
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "snacky-product-card__footer-row", children: [
1274
- /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("span", { className: "snacky-product-card__rating", children: [
1275
- /* @__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 }) }),
1276
1365
  rating
1277
1366
  ] }),
1278
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
1367
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1279
1368
  IconButton,
1280
1369
  {
1281
1370
  variant: "primary",
1282
- 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 }),
1283
1372
  onClick: (e) => {
1284
1373
  e.stopPropagation();
1285
1374
  onAddToCart();
@@ -1294,17 +1383,17 @@ function ProductCard(props) {
1294
1383
  }
1295
1384
 
1296
1385
  // src/components/ImagePlaceholder/ImagePlaceholder.tsx
1297
- var import_jsx_runtime34 = require("react/jsx-runtime");
1386
+ var import_jsx_runtime35 = require("react/jsx-runtime");
1298
1387
  function ImagePlaceholder({ width, height, className }) {
1299
1388
  const iconSize = Math.round(Math.min(width, height) * 0.32);
1300
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(
1389
+ return /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
1301
1390
  "div",
1302
1391
  {
1303
1392
  className: cx("snacky-image-placeholder", className),
1304
1393
  style: { width, height },
1305
1394
  role: "img",
1306
1395
  "aria-label": "No image",
1307
- children: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
1396
+ children: /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
1308
1397
  "svg",
1309
1398
  {
1310
1399
  width: iconSize,
@@ -1316,9 +1405,9 @@ function ImagePlaceholder({ width, height, className }) {
1316
1405
  strokeLinecap: "round",
1317
1406
  strokeLinejoin: "round",
1318
1407
  children: [
1319
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
1320
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("circle", { cx: "8.5", cy: "8.5", r: "1.5", fill: "currentColor", stroke: "none" }),
1321
- /* @__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" })
1322
1411
  ]
1323
1412
  }
1324
1413
  )
@@ -1356,6 +1445,7 @@ function ImagePlaceholder({ width, height, className }) {
1356
1445
  PointBalanceBanner,
1357
1446
  ProductCard,
1358
1447
  ProductChip,
1448
+ ProductGroupSection,
1359
1449
  ProductImage,
1360
1450
  RadioOption,
1361
1451
  SearchField,