@butternutbox/pawprint-native 0.25.1 → 1.1.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.
Files changed (34) hide show
  1. package/.turbo/turbo-build.log +7 -7
  2. package/CHANGELOG.md +20 -0
  3. package/dist/index.cjs +19 -50
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +36 -10
  6. package/dist/index.d.ts +36 -10
  7. package/dist/index.js +9 -37
  8. package/dist/index.js.map +1 -1
  9. package/package.json +2 -1
  10. package/src/components/atoms/Button/Button.tsx +1 -1
  11. package/src/components/atoms/Input/InputField.tsx +1 -1
  12. package/src/components/atoms/TextArea/TextAreaField.tsx +1 -1
  13. package/src/components/atoms/Typography/Typography.tsx +1 -1
  14. package/src/components/molecules/NativeSelectPicker/NativeSelectPicker.styled.ts +1 -1
  15. package/src/components/molecules/NumberField/NumberFieldInput.tsx +1 -1
  16. package/src/components/molecules/ProductDisplayCard/ProductDisplayCard.stories.tsx +40 -5
  17. package/src/components/molecules/ProductDisplayCard/ProductDisplayCard.test.tsx +55 -0
  18. package/src/components/molecules/ProductDisplayCard/ProductDisplayCard.tsx +35 -9
  19. package/src/index.ts +1 -1
  20. package/tsup.config.ts +1 -2
  21. package/dist/ibm-plex-sans-condensed-400-normal.ttf +0 -0
  22. package/dist/ibm-plex-sans-condensed-500-normal.ttf +0 -0
  23. package/dist/ibm-plex-sans-condensed-600-normal.ttf +0 -0
  24. package/dist/ibm-plex-sans-condensed-700-normal.ttf +0 -0
  25. package/dist/ida-narrow-500-normal.ttf +0 -0
  26. package/dist/ida-narrow-700-normal.ttf +0 -0
  27. package/src/fonts/ibm-plex-sans-condensed-400-normal.ttf +0 -0
  28. package/src/fonts/ibm-plex-sans-condensed-500-normal.ttf +0 -0
  29. package/src/fonts/ibm-plex-sans-condensed-600-normal.ttf +0 -0
  30. package/src/fonts/ibm-plex-sans-condensed-700-normal.ttf +0 -0
  31. package/src/fonts/ida-narrow-500-normal.ttf +0 -0
  32. package/src/fonts/ida-narrow-700-normal.ttf +0 -0
  33. package/src/fonts/index.ts +0 -48
  34. package/src/types/fonts.d.ts +0 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@butternutbox/pawprint-native",
3
- "version": "0.25.1",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "ButternutBox Pawprint Design System - React Native Components",
6
6
  "main": "./dist/index.cjs",
@@ -22,6 +22,7 @@
22
22
  "test": "vitest run"
23
23
  },
24
24
  "dependencies": {
25
+ "@butternutbox/pawprint-fonts": "^2.0.0",
25
26
  "@butternutbox/pawprint-tokens": "^0.5.0",
26
27
  "@emotion/native": "^11.11.0",
27
28
  "@emotion/react": "^11.14.0",
@@ -4,7 +4,7 @@ import styled from "@emotion/native"
4
4
  import { useTheme } from "@emotion/react"
5
5
  import { Typography } from "../Typography"
6
6
  import { Spinner } from "../Spinner"
7
- import { resolveFont } from "../../../fonts"
7
+ import { resolveFont } from "@butternutbox/pawprint-fonts"
8
8
 
9
9
  type ButtonVariant = "filled" | "outlined" | "text"
10
10
  type ButtonSize = "sm" | "md" | "lg"
@@ -13,7 +13,7 @@ import {
13
13
  Error as ErrorIcon,
14
14
  CheckCircle as SuccessIcon
15
15
  } from "@butternutbox/pawprint-icons/core"
16
- import { resolveFont } from "../../../fonts"
16
+ import { resolveFont } from "@butternutbox/pawprint-fonts"
17
17
  import { Icon } from "../Icon"
18
18
 
19
19
  export type InputState = "default" | "error" | "success"
@@ -13,7 +13,7 @@ import {
13
13
  Error as ErrorIcon,
14
14
  CheckCircle as SuccessIcon
15
15
  } from "@butternutbox/pawprint-icons/core"
16
- import { resolveFont } from "../../../fonts"
16
+ import { resolveFont } from "@butternutbox/pawprint-fonts"
17
17
  import { Icon } from "../Icon"
18
18
  import type { InputState } from "../Input/InputField"
19
19
 
@@ -3,7 +3,7 @@ import { Text, TextStyle, TextProps } from "react-native"
3
3
  import { useTheme } from "@emotion/react"
4
4
  import styled from "@emotion/native"
5
5
  import { type BrandThemeSemanticsColourText } from "@butternutbox/pawprint-tokens"
6
- import { resolveFont } from "../../../fonts"
6
+ import { resolveFont } from "@butternutbox/pawprint-fonts"
7
7
 
8
8
  type BodySize = "xs" | "sm" | "md" | "lg"
9
9
  type HeadingSize = "2xs" | "xs" | "sm" | "md" | "lg" | "xl"
@@ -3,7 +3,7 @@ import { View, Text } from "react-native"
3
3
  import Animated from "react-native-reanimated"
4
4
  import { DEFAULT_THEME_OPTIONS } from "@butternutbox/pawprint-tokens"
5
5
 
6
- import { resolveFont } from "../../../fonts"
6
+ import { resolveFont } from "@butternutbox/pawprint-fonts"
7
7
 
8
8
  type AndroidOptionTextProps = {
9
9
  isSelected: boolean
@@ -4,7 +4,7 @@ import styled from "@emotion/native"
4
4
  import { useTheme } from "@emotion/react"
5
5
  import { IconButton } from "../../atoms/IconButton"
6
6
  import { Add, Remove } from "@butternutbox/pawprint-icons/core"
7
- import { resolveFont } from "../../../fonts"
7
+ import { resolveFont } from "@butternutbox/pawprint-fonts"
8
8
  import { Typography } from "../../atoms/Typography"
9
9
 
10
10
  type NumberFieldSize = "sm" | "lg"
@@ -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"
@@ -242,4 +242,59 @@ describe("ProductDisplayCard (Native)", () => {
242
242
  // ...and the picker input is not rendered.
243
243
  expect(screen.queryByDisplayValue("3")).toBeFalsy()
244
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("suppresses the quantity picker when thumbnailBadge is set", () => {
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.queryByDisplayValue("2")).toBeFalsy()
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()
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,25 @@ 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
23
  * current `quantity`) instead of the quantity picker. Defaults to `false`.
21
24
  * The badge and the quantity picker are mutually exclusive — only one shows
22
25
  * at a time; when `quantityBadge` is true the picker is never rendered.
23
26
  */
24
27
  quantityBadge?: boolean
28
+ /**
29
+ * Render a `Badge` overlaid on the top-right of the image. Accepts any
30
+ * `BadgeProps` (variant, size, icon, children, etc.) — pass them as an object.
31
+ * Mutually exclusive with the quantity picker: when set, the picker is never
32
+ * rendered.
33
+ *
34
+ * @example
35
+ * thumbnailBadge={{ variant: "primary", size: "large", children: "New" }}
36
+ */
37
+ thumbnailBadge?: BadgeProps
25
38
  disableQuantityButtons?: boolean
26
39
  hideQuantityButtons?: boolean
27
40
  incrementDisabled?: boolean
@@ -194,9 +207,14 @@ const StyledNoneBanner = styled(View)(({ theme }) => {
194
207
  * @param quantity - *(optional)* Current quantity value
195
208
  * @param onQuantityChange - *(optional)* Callback when quantity changes
196
209
  * @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.
210
+ * @param quantityBadge - *(optional)* **@deprecated** — use `thumbnailBadge` instead.
211
+ * Show a legacy quantity badge over the image (renders the current `quantity`
212
+ * as a primary/large Badge). Mutually exclusive with the picker; defaults to
213
+ * `false`. When both `thumbnailBadge` and `quantityBadge` are set, `thumbnailBadge`
214
+ * wins.
215
+ * @param thumbnailBadge - *(optional)* Render a `Badge` overlaid on the top-right
216
+ * of the image. Accepts any `BadgeProps` (`variant`, `size`, `icon`, `children`, …).
217
+ * Mutually exclusive with the quantity picker.
200
218
  * @param quantityStep - *(optional)* Increment/decrement amount (default: 1)
201
219
  * @param image - *(optional)* Image URL string or React element
202
220
  * @param banner - *(optional)* Banner content string or React element to show below card
@@ -217,6 +235,7 @@ export const ProductDisplayCard = React.forwardRef<
217
235
  onQuantityChange,
218
236
  showQuantityPicker = false,
219
237
  quantityBadge = false,
238
+ thumbnailBadge,
220
239
  disableQuantityButtons = false,
221
240
  hideQuantityButtons = false,
222
241
  incrementDisabled = false,
@@ -241,6 +260,15 @@ export const ProductDisplayCard = React.forwardRef<
241
260
  }
242
261
  }
243
262
 
263
+ // `thumbnailBadge` supersedes the deprecated `quantityBadge`. When set,
264
+ // consumers control the entire badge; when unset, the legacy `quantityBadge`
265
+ // boolean falls back to the previous hardcoded quantity chip.
266
+ const resolvedBadgeProps: BadgeProps | undefined =
267
+ thumbnailBadge ??
268
+ (quantityBadge
269
+ ? { variant: "primary", size: "large", children: String(quantity) }
270
+ : undefined)
271
+
244
272
  const cardContent = (
245
273
  <StyledCardContainer
246
274
  ref={ref}
@@ -262,11 +290,9 @@ export const ProductDisplayCard = React.forwardRef<
262
290
  ) : (
263
291
  image
264
292
  )}
265
- {quantityBadge && (
293
+ {resolvedBadgeProps && (
266
294
  <StyledBadgeOverlay pointerEvents="none">
267
- <Badge variant="primary" size="large">
268
- {String(quantity)}
269
- </Badge>
295
+ <Badge {...resolvedBadgeProps} />
270
296
  </StyledBadgeOverlay>
271
297
  )}
272
298
  </StyledImage>
@@ -286,7 +312,7 @@ export const ProductDisplayCard = React.forwardRef<
286
312
  )}
287
313
  </StyledTextContainer>
288
314
 
289
- {showQuantityPicker && !quantityBadge && (
315
+ {showQuantityPicker && !resolvedBadgeProps && (
290
316
  <View style={{ marginLeft: "auto" }}>
291
317
  <NumberField
292
318
  size="sm"
package/src/index.ts CHANGED
@@ -4,7 +4,7 @@ export { tokens } from "@butternutbox/pawprint-tokens"
4
4
 
5
5
  export { default as styled } from "@emotion/native"
6
6
 
7
- export * from "./fonts"
7
+ export * from "@butternutbox/pawprint-fonts"
8
8
  export * from "./theme"
9
9
  export * from "./components"
10
10
  export * from "./hooks"
package/tsup.config.ts CHANGED
@@ -14,10 +14,9 @@ export default defineConfig({
14
14
  "react-native-svg",
15
15
  "react-native-worklets",
16
16
  /^@rn-primitives\/.*/,
17
- /\.ttf$/
17
+ /^@butternutbox\/pawprint-fonts/
18
18
  ],
19
19
  treeshake: true,
20
- onSuccess: "cp src/fonts/*.ttf dist/",
21
20
  esbuildOptions(options) {
22
21
  options.conditions = ["react-native", "import", "module", "default"]
23
22
  options.loader = {
Binary file
Binary file
Binary file
Binary file
@@ -1,48 +0,0 @@
1
- import { Brand, BrandName } from "@butternutbox/pawprint-tokens"
2
- import type { FontSource } from "expo-font"
3
-
4
- import idaNarrow500 from "./ida-narrow-500-normal.ttf"
5
- import idaNarrow700 from "./ida-narrow-700-normal.ttf"
6
- import ibmPlexSansCondensed400 from "./ibm-plex-sans-condensed-400-normal.ttf"
7
- import ibmPlexSansCondensed500 from "./ibm-plex-sans-condensed-500-normal.ttf"
8
- import ibmPlexSansCondensed600 from "./ibm-plex-sans-condensed-600-normal.ttf"
9
- import ibmPlexSansCondensed700 from "./ibm-plex-sans-condensed-700-normal.ttf"
10
-
11
- // Keys passed to useFonts — each key becomes the exact @font-face family name
12
- // that expo-font registers on web, and the postscript font name on iOS/Android.
13
- export const BRAND_FONTS: Partial<
14
- Record<BrandName, Record<string, FontSource>>
15
- > = {
16
- [Brand.Butternutbox]: {
17
- "IdaWebNarrow-Medium": idaNarrow500 as FontSource,
18
- "IdaWebNarrow-Bold": idaNarrow700 as FontSource,
19
- "IBMPlexSansCond-Regular": ibmPlexSansCondensed400 as FontSource,
20
- "IBMPlexSansCond-Medium": ibmPlexSansCondensed500 as FontSource,
21
- "IBMPlexSansCond-SemiBold": ibmPlexSansCondensed600 as FontSource,
22
- "IBMPlexSansCond-Bold": ibmPlexSansCondensed700 as FontSource
23
- }
24
- }
25
-
26
- // Maps token fontFamily + fontWeight → PostScript font name.
27
- // Keys must match the PostScript name embedded in the font file — iOS registers
28
- // fonts under their PostScript name regardless of the useFonts key.
29
- const FONT_MAP: Record<string, Record<string, string>> = {
30
- "IBM Plex Sans Condensed": {
31
- "400": "IBMPlexSansCond-Regular",
32
- "500": "IBMPlexSansCond-Medium",
33
- "600": "IBMPlexSansCond-SemiBold",
34
- "700": "IBMPlexSansCond-Bold"
35
- },
36
- "Ida Narrow": {
37
- "500": "IdaWebNarrow-Medium",
38
- "700": "IdaWebNarrow-Bold"
39
- }
40
- }
41
-
42
- export const resolveFont = (
43
- fontFamily: string,
44
- fontWeight: string
45
- ): { fontFamily: string } => {
46
- const resolved = FONT_MAP[fontFamily]?.[fontWeight]
47
- return { fontFamily: resolved ?? fontFamily }
48
- }
@@ -1,4 +0,0 @@
1
- declare module "*.ttf" {
2
- const src: string
3
- export default src
4
- }