@butternutbox/pawprint-native 1.0.0 → 1.1.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@butternutbox/pawprint-native",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "type": "module",
5
5
  "description": "ButternutBox Pawprint Design System - React Native Components",
6
6
  "main": "./dist/index.cjs",
@@ -38,7 +38,12 @@ export default {
38
38
  quantityBadge: {
39
39
  control: { type: "boolean" },
40
40
  description:
41
- "Show a quantity badge over the image instead of the picker (mutually exclusive with the quantity picker)"
41
+ "@deprecated — use `thumbnailBadge`. Show a legacy quantity badge over the image instead of the picker."
42
+ },
43
+ thumbnailBadge: {
44
+ control: false,
45
+ description:
46
+ "Render a Badge over the image (accepts any BadgeProps). Mutually exclusive with the quantity picker."
42
47
  },
43
48
  disableQuantityButtons: {
44
49
  control: { type: "boolean" },
@@ -277,10 +282,42 @@ export const WithCustomBanner = () => (
277
282
  </View>
278
283
  )
279
284
 
285
+ export const WithThumbnailBadge = () => (
286
+ <View style={styles.column}>
287
+ {/* Consumer controls the badge content and variant. */}
288
+ <ProductDisplayCard
289
+ title="Recipe Name"
290
+ subtext="+£0.00/pouch"
291
+ showSubtext
292
+ thumbnailBadge={{ variant: "primary", size: "large", children: "New" }}
293
+ image={placeholderImageUrl}
294
+ />
295
+ <ProductDisplayCard
296
+ title="Recipe Name"
297
+ subtext="+£0.00/pouch"
298
+ showSubtext
299
+ thumbnailBadge={{ variant: "promo", size: "large", children: "Popular" }}
300
+ image={placeholderImageUrl}
301
+ />
302
+ {/* Suppresses the picker even when showQuantityPicker is set. */}
303
+ <ProductDisplayCard
304
+ title="Recipe Name"
305
+ subtext="+£0.00/pouch"
306
+ showSubtext
307
+ quantity={2}
308
+ showQuantityPicker
309
+ thumbnailBadge={{ variant: "primary", size: "large", children: "2" }}
310
+ image={placeholderImageUrl}
311
+ />
312
+ </View>
313
+ )
314
+
315
+ /**
316
+ * @deprecated Prefer `thumbnailBadge`. This story documents the legacy
317
+ * `quantityBadge` prop behaviour during the deprecation window.
318
+ */
280
319
  export const WithQuantityBadge = () => (
281
320
  <View style={styles.column}>
282
- {/* OOB recipe / Extras card: quantity shown as a badge over the image
283
- instead of the picker. The two are mutually exclusive. */}
284
321
  <ProductDisplayCard
285
322
  title="Recipe Name"
286
323
  subtext="+£0.00/pouch"
@@ -289,8 +326,6 @@ export const WithQuantityBadge = () => (
289
326
  quantityBadge
290
327
  image={placeholderImageUrl}
291
328
  />
292
- {/* Even with showQuantityPicker set, the badge wins and the picker is
293
- suppressed. */}
294
329
  <ProductDisplayCard
295
330
  title="Recipe Name"
296
331
  subtext="+£0.00/pouch"
@@ -222,7 +222,7 @@ describe("ProductDisplayCard (Native)", () => {
222
222
  expect(screen.queryByText("6")).toBeFalsy()
223
223
  })
224
224
 
225
- it("suppresses the quantity picker when quantityBadge is set (mutually exclusive)", () => {
225
+ it("renders the quantity picker alongside a quantityBadge", () => {
226
226
  renderWithTheme(
227
227
  <ProductDisplayCard
228
228
  title="Test Product"
@@ -239,7 +239,62 @@ describe("ProductDisplayCard (Native)", () => {
239
239
 
240
240
  // The badge shows the quantity...
241
241
  expect(screen.getByText("3")).toBeTruthy()
242
- // ...and the picker input is not rendered.
243
- expect(screen.queryByDisplayValue("3")).toBeFalsy()
242
+ // ...and the picker is rendered independently of it.
243
+ expect(screen.getByDisplayValue("3")).toBeTruthy()
244
+ })
245
+
246
+ it("renders a thumbnailBadge from BadgeProps", () => {
247
+ renderWithTheme(
248
+ <ProductDisplayCard
249
+ title="Test Product"
250
+ thumbnailBadge={{ variant: "primary", size: "large", children: "New" }}
251
+ image={
252
+ <View>
253
+ <Text>Image</Text>
254
+ </View>
255
+ }
256
+ />
257
+ )
258
+
259
+ expect(screen.getByText("New")).toBeTruthy()
260
+ })
261
+
262
+ it("renders the quantity picker alongside a thumbnailBadge", () => {
263
+ renderWithTheme(
264
+ <ProductDisplayCard
265
+ title="Test Product"
266
+ quantity={2}
267
+ thumbnailBadge={{ children: "New" }}
268
+ showQuantityPicker
269
+ image={
270
+ <View>
271
+ <Text>Image</Text>
272
+ </View>
273
+ }
274
+ />
275
+ )
276
+
277
+ expect(screen.getByText("New")).toBeTruthy()
278
+ expect(screen.getByDisplayValue("2")).toBeTruthy()
279
+ })
280
+
281
+ it("prefers thumbnailBadge over the deprecated quantityBadge when both are set", () => {
282
+ renderWithTheme(
283
+ <ProductDisplayCard
284
+ title="Test Product"
285
+ quantity={9}
286
+ quantityBadge
287
+ thumbnailBadge={{ children: "Popular" }}
288
+ image={
289
+ <View>
290
+ <Text>Image</Text>
291
+ </View>
292
+ }
293
+ />
294
+ )
295
+
296
+ expect(screen.getByText("Popular")).toBeTruthy()
297
+ // The legacy quantity badge should not render alongside it.
298
+ expect(screen.queryByText("9")).toBeFalsy()
244
299
  })
245
300
  })
@@ -3,7 +3,7 @@ import { View, ViewProps, Image, Pressable } from "react-native"
3
3
  import styled from "@emotion/native"
4
4
  import { useTheme } from "@emotion/react"
5
5
  import { Typography } from "../../atoms/Typography"
6
- import { Badge } from "../../atoms/Badge"
6
+ import { Badge, type BadgeProps } from "../../atoms/Badge"
7
7
  import { NumberField } from "../../molecules/NumberField"
8
8
  import { Notification } from "../Notification"
9
9
  import { parseTokenValue } from "../../../utils"
@@ -16,12 +16,23 @@ export type ProductDisplayCardProps = ViewProps & {
16
16
  onQuantityChange?: (quantity: number) => void
17
17
  showQuantityPicker?: boolean
18
18
  /**
19
+ * @deprecated Use `thumbnailBadge` instead. When both are set, `thumbnailBadge`
20
+ * wins. This prop will be removed in a future major release.
21
+ *
19
22
  * Show a quantity badge overlaid on the top-right of the image (rendering the
20
- * current `quantity`) instead of the quantity picker. Defaults to `false`.
21
- * The badge and the quantity picker are mutually exclusive — only one shows
22
- * at a time; when `quantityBadge` is true the picker is never rendered.
23
+ * current `quantity`). Defaults to `false`. Independent of the quantity
24
+ * picker — use `showQuantityPicker` to control that separately.
23
25
  */
24
26
  quantityBadge?: boolean
27
+ /**
28
+ * Render a `Badge` overlaid on the top-right of the image. Accepts any
29
+ * `BadgeProps` (variant, size, icon, children, etc.) — pass them as an object.
30
+ * Independent of the quantity picker: both can be shown at the same time.
31
+ *
32
+ * @example
33
+ * thumbnailBadge={{ variant: "primary", size: "large", children: "New" }}
34
+ */
35
+ thumbnailBadge?: BadgeProps
25
36
  disableQuantityButtons?: boolean
26
37
  hideQuantityButtons?: boolean
27
38
  incrementDisabled?: boolean
@@ -194,9 +205,14 @@ const StyledNoneBanner = styled(View)(({ theme }) => {
194
205
  * @param quantity - *(optional)* Current quantity value
195
206
  * @param onQuantityChange - *(optional)* Callback when quantity changes
196
207
  * @param showQuantityPicker - *(optional)* Show/hide quantity picker
197
- * @param quantityBadge - *(optional)* Show a quantity badge over the image instead
198
- * of the picker (renders the current `quantity`). Mutually exclusive with the
199
- * quantity picker; defaults to `false`. Used on OOB recipe and Extras cards.
208
+ * @param quantityBadge - *(optional)* **@deprecated** — use `thumbnailBadge` instead.
209
+ * Show a legacy quantity badge over the image (renders the current `quantity`
210
+ * as a primary/large Badge). Mutually exclusive with the picker; defaults to
211
+ * `false`. When both `thumbnailBadge` and `quantityBadge` are set, `thumbnailBadge`
212
+ * wins.
213
+ * @param thumbnailBadge - *(optional)* Render a `Badge` overlaid on the top-right
214
+ * of the image. Accepts any `BadgeProps` (`variant`, `size`, `icon`, `children`, …).
215
+ * Mutually exclusive with the quantity picker.
200
216
  * @param quantityStep - *(optional)* Increment/decrement amount (default: 1)
201
217
  * @param image - *(optional)* Image URL string or React element
202
218
  * @param banner - *(optional)* Banner content string or React element to show below card
@@ -217,6 +233,7 @@ export const ProductDisplayCard = React.forwardRef<
217
233
  onQuantityChange,
218
234
  showQuantityPicker = false,
219
235
  quantityBadge = false,
236
+ thumbnailBadge,
220
237
  disableQuantityButtons = false,
221
238
  hideQuantityButtons = false,
222
239
  incrementDisabled = false,
@@ -241,6 +258,15 @@ export const ProductDisplayCard = React.forwardRef<
241
258
  }
242
259
  }
243
260
 
261
+ // `thumbnailBadge` supersedes the deprecated `quantityBadge`. When set,
262
+ // consumers control the entire badge; when unset, the legacy `quantityBadge`
263
+ // boolean falls back to the previous hardcoded quantity chip.
264
+ const resolvedBadgeProps: BadgeProps | undefined =
265
+ thumbnailBadge ??
266
+ (quantityBadge
267
+ ? { variant: "primary", size: "large", children: String(quantity) }
268
+ : undefined)
269
+
244
270
  const cardContent = (
245
271
  <StyledCardContainer
246
272
  ref={ref}
@@ -262,11 +288,9 @@ export const ProductDisplayCard = React.forwardRef<
262
288
  ) : (
263
289
  image
264
290
  )}
265
- {quantityBadge && (
291
+ {resolvedBadgeProps && (
266
292
  <StyledBadgeOverlay pointerEvents="none">
267
- <Badge variant="primary" size="large">
268
- {String(quantity)}
269
- </Badge>
293
+ <Badge {...resolvedBadgeProps} />
270
294
  </StyledBadgeOverlay>
271
295
  )}
272
296
  </StyledImage>
@@ -286,7 +310,7 @@ export const ProductDisplayCard = React.forwardRef<
286
310
  )}
287
311
  </StyledTextContainer>
288
312
 
289
- {showQuantityPicker && !quantityBadge && (
313
+ {showQuantityPicker && (
290
314
  <View style={{ marginLeft: "auto" }}>
291
315
  <NumberField
292
316
  size="sm"