@butternutbox/pawprint-native 0.19.2 → 0.20.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@butternutbox/pawprint-native",
3
- "version": "0.19.2",
3
+ "version": "0.20.0",
4
4
  "type": "module",
5
5
  "description": "ButternutBox Pawprint Design System - React Native Components",
6
6
  "main": "./dist/index.cjs",
@@ -35,6 +35,11 @@ export default {
35
35
  control: { type: "boolean" },
36
36
  description: "Show/hide quantity picker"
37
37
  },
38
+ quantityBadge: {
39
+ control: { type: "boolean" },
40
+ description:
41
+ "Show a quantity badge over the image instead of the picker (mutually exclusive with the quantity picker)"
42
+ },
38
43
  disableQuantityButtons: {
39
44
  control: { type: "boolean" },
40
45
  description: "Disable quantity buttons"
@@ -271,3 +276,29 @@ export const WithCustomBanner = () => (
271
276
  />
272
277
  </View>
273
278
  )
279
+
280
+ export const WithQuantityBadge = () => (
281
+ <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
+ <ProductDisplayCard
285
+ title="Recipe Name"
286
+ subtext="+£0.00/pouch"
287
+ showSubtext
288
+ quantity={6}
289
+ quantityBadge
290
+ image={placeholderImageUrl}
291
+ />
292
+ {/* Even with showQuantityPicker set, the badge wins and the picker is
293
+ suppressed. */}
294
+ <ProductDisplayCard
295
+ title="Recipe Name"
296
+ subtext="+£0.00/pouch"
297
+ showSubtext
298
+ quantity={2}
299
+ quantityBadge
300
+ showQuantityPicker
301
+ image={placeholderImageUrl}
302
+ />
303
+ </View>
304
+ )
@@ -188,4 +188,58 @@ describe("ProductDisplayCard (Native)", () => {
188
188
 
189
189
  expect(screen.getByText("Test Banner")).toBeTruthy()
190
190
  })
191
+
192
+ it("renders the quantity badge over the image when quantityBadge is true", () => {
193
+ renderWithTheme(
194
+ <ProductDisplayCard
195
+ title="Test Product"
196
+ quantity={6}
197
+ quantityBadge
198
+ image={
199
+ <View>
200
+ <Text>Image</Text>
201
+ </View>
202
+ }
203
+ />
204
+ )
205
+
206
+ expect(screen.getByText("6")).toBeTruthy()
207
+ })
208
+
209
+ it("does not render the quantity badge by default", () => {
210
+ renderWithTheme(
211
+ <ProductDisplayCard
212
+ title="Test Product"
213
+ quantity={6}
214
+ image={
215
+ <View>
216
+ <Text>Image</Text>
217
+ </View>
218
+ }
219
+ />
220
+ )
221
+
222
+ expect(screen.queryByText("6")).toBeFalsy()
223
+ })
224
+
225
+ it("suppresses the quantity picker when quantityBadge is set (mutually exclusive)", () => {
226
+ renderWithTheme(
227
+ <ProductDisplayCard
228
+ title="Test Product"
229
+ quantity={3}
230
+ quantityBadge
231
+ showQuantityPicker
232
+ image={
233
+ <View>
234
+ <Text>Image</Text>
235
+ </View>
236
+ }
237
+ />
238
+ )
239
+
240
+ // The badge shows the quantity...
241
+ expect(screen.getByText("3")).toBeTruthy()
242
+ // ...and the picker input is not rendered.
243
+ expect(screen.queryByDisplayValue("3")).toBeFalsy()
244
+ })
191
245
  })
@@ -3,6 +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
7
  import { NumberField } from "../../molecules/NumberField"
7
8
  import { Notification } from "../Notification"
8
9
  import { parseTokenValue } from "../../../utils"
@@ -14,6 +15,13 @@ export type ProductDisplayCardProps = ViewProps & {
14
15
  quantity?: number
15
16
  onQuantityChange?: (quantity: number) => void
16
17
  showQuantityPicker?: boolean
18
+ /**
19
+ * 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
+ */
24
+ quantityBadge?: boolean
17
25
  disableQuantityButtons?: boolean
18
26
  hideQuantityButtons?: boolean
19
27
  incrementDisabled?: boolean
@@ -77,6 +85,20 @@ const StyledImage = styled(View)<{
77
85
  position: "relative"
78
86
  }))
79
87
 
88
+ // Anchors the quantity badge to the top-right corner of the image, inset by
89
+ // `spacing.2xs` (4px) per the Figma spec (node 10864:736).
90
+ const StyledBadgeOverlay = styled(View)(({ theme }) => {
91
+ const offset = parseTokenValue(
92
+ theme.tokens.semantics.dimensions.spacing["2xs"]
93
+ )
94
+ return {
95
+ position: "absolute",
96
+ top: offset,
97
+ right: offset,
98
+ zIndex: 2
99
+ }
100
+ })
101
+
80
102
  const StyledContentArea = styled(View)(({ theme }) => {
81
103
  const { spacing } = theme.tokens.semantics.dimensions
82
104
  const paddingValue = parseTokenValue(spacing.md)
@@ -171,6 +193,9 @@ const StyledNoneBanner = styled(View)(({ theme }) => {
171
193
  * @param quantity - *(optional)* Current quantity value
172
194
  * @param onQuantityChange - *(optional)* Callback when quantity changes
173
195
  * @param showQuantityPicker - *(optional)* Show/hide quantity picker
196
+ * @param quantityBadge - *(optional)* Show a quantity badge over the image instead
197
+ * of the picker (renders the current `quantity`). Mutually exclusive with the
198
+ * quantity picker; defaults to `false`. Used on OOB recipe and Extras cards.
174
199
  * @param image - *(optional)* Image URL string or React element
175
200
  * @param banner - *(optional)* Banner content string or React element to show below card
176
201
  * @param showBanner - *(optional)* Show/hide banner
@@ -189,6 +214,7 @@ export const ProductDisplayCard = React.forwardRef<
189
214
  quantity = 1,
190
215
  onQuantityChange,
191
216
  showQuantityPicker = false,
217
+ quantityBadge = false,
192
218
  disableQuantityButtons = false,
193
219
  hideQuantityButtons = false,
194
220
  incrementDisabled = false,
@@ -233,6 +259,13 @@ export const ProductDisplayCard = React.forwardRef<
233
259
  ) : (
234
260
  image
235
261
  )}
262
+ {quantityBadge && (
263
+ <StyledBadgeOverlay pointerEvents="none">
264
+ <Badge variant="primary" size="large">
265
+ {String(quantity)}
266
+ </Badge>
267
+ </StyledBadgeOverlay>
268
+ )}
236
269
  </StyledImage>
237
270
  </Pressable>
238
271
  )}
@@ -250,7 +283,7 @@ export const ProductDisplayCard = React.forwardRef<
250
283
  )}
251
284
  </StyledTextContainer>
252
285
 
253
- {showQuantityPicker && (
286
+ {showQuantityPicker && !quantityBadge && (
254
287
  <View style={{ marginLeft: "auto" }}>
255
288
  <NumberField
256
289
  size="sm"