@butternutbox/pawprint-native 0.3.2 → 0.4.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.
Files changed (31) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +14 -0
  3. package/dist/index.cjs +1012 -173
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +252 -5
  6. package/dist/index.d.ts +252 -5
  7. package/dist/index.js +1007 -173
  8. package/dist/index.js.map +1 -1
  9. package/package.json +2 -1
  10. package/src/components/atoms/Hint/Hint.tsx +1 -2
  11. package/src/components/atoms/Input/InputField.tsx +7 -1
  12. package/src/components/molecules/Animated/Animated.tsx +12 -3
  13. package/src/components/molecules/Countdown/Countdown.stories.tsx +218 -0
  14. package/src/components/molecules/Countdown/Countdown.tsx +315 -0
  15. package/src/components/molecules/Countdown/index.ts +2 -0
  16. package/src/components/molecules/NumberField/NumberFieldInput.tsx +14 -24
  17. package/src/components/molecules/ProductDisplayCard/ProductDisplayCard.stories.tsx +275 -0
  18. package/src/components/molecules/ProductDisplayCard/ProductDisplayCard.test.tsx +198 -0
  19. package/src/components/molecules/ProductDisplayCard/ProductDisplayCard.tsx +292 -0
  20. package/src/components/molecules/ProductDisplayCard/index.ts +5 -0
  21. package/src/components/molecules/ProductListingCard/Badge.tsx +65 -0
  22. package/src/components/molecules/ProductListingCard/Grid.tsx +59 -0
  23. package/src/components/molecules/ProductListingCard/ProductListingCard.stories.tsx +209 -0
  24. package/src/components/molecules/ProductListingCard/ProductListingCard.tsx +235 -0
  25. package/src/components/molecules/ProductListingCard/index.ts +2 -0
  26. package/src/components/molecules/TabNavigation/TabNavigation.stories.tsx +183 -0
  27. package/src/components/molecules/TabNavigation/TabNavigation.tsx +354 -0
  28. package/src/components/molecules/TabNavigation/index.ts +7 -0
  29. package/src/components/molecules/index.ts +4 -0
  30. package/src/utils/index.ts +1 -0
  31. package/src/utils/token.ts +1 -0
@@ -0,0 +1,275 @@
1
+ import React from "react"
2
+ import { View, StyleSheet } from "react-native"
3
+ import { ProductDisplayCard } from "./ProductDisplayCard"
4
+ import type { ProductDisplayCardProps } from "./ProductDisplayCard"
5
+
6
+ const placeholderImageUrl = "https://placeholder.co/100x100?text=Recipe"
7
+
8
+ const styles = StyleSheet.create({
9
+ column: { flexDirection: "column", gap: 24, padding: 16 }
10
+ })
11
+
12
+ export default {
13
+ title: "Molecules/ProductDisplayCard",
14
+ component: ProductDisplayCard,
15
+ argTypes: {
16
+ device: {
17
+ control: { type: "select" },
18
+ options: ["desktop", "mobile"],
19
+ description: "Device variant affecting sizing and typography"
20
+ },
21
+ title: {
22
+ control: { type: "text" },
23
+ description: "Product title"
24
+ },
25
+ subtext: {
26
+ control: { type: "text" },
27
+ description: "Secondary text (e.g. price)"
28
+ },
29
+ showSubtext: {
30
+ control: { type: "boolean" },
31
+ description: "Show/hide subtext"
32
+ },
33
+ quantity: {
34
+ control: { type: "number", min: 1 },
35
+ description: "Current quantity"
36
+ },
37
+ showQuantityPicker: {
38
+ control: { type: "boolean" },
39
+ description: "Show/hide quantity picker"
40
+ },
41
+ showImageQuantityBadge: {
42
+ control: { type: "boolean" },
43
+ description: "Show/hide quantity badge overlay on image"
44
+ },
45
+ disableQuantityButtons: {
46
+ control: { type: "boolean" },
47
+ description: "Disable quantity buttons"
48
+ },
49
+ hideQuantityButtons: {
50
+ control: { type: "boolean" },
51
+ description: "Hide quantity buttons"
52
+ },
53
+ showIncrementButton: {
54
+ control: { type: "boolean" },
55
+ description: "Show/hide increment button"
56
+ },
57
+ showDecrementButton: {
58
+ control: { type: "boolean" },
59
+ description: "Show/hide decrement button"
60
+ },
61
+ incrementDisabled: {
62
+ control: { type: "boolean" },
63
+ description: "Disable increment button"
64
+ },
65
+ decrementDisabled: {
66
+ control: { type: "boolean" },
67
+ description: "Disable decrement button"
68
+ },
69
+ showBanner: {
70
+ control: { type: "boolean" },
71
+ description: "Show/hide banner below card"
72
+ },
73
+ onQuantityChange: {
74
+ control: false,
75
+ description: "Callback when quantity changes"
76
+ },
77
+ image: {
78
+ control: false,
79
+ description: "Image element or content"
80
+ },
81
+ banner: {
82
+ control: { type: "text" },
83
+ description: "Banner content to show below card"
84
+ },
85
+ bannerType: {
86
+ control: { type: "select" },
87
+ options: ["error", "success", "warning", "info"],
88
+ description: "Banner notification type"
89
+ },
90
+ showBannerIcon: {
91
+ control: { type: "boolean" },
92
+ description: "Show/hide banner notification icon"
93
+ }
94
+ }
95
+ }
96
+
97
+ export const Playground = (args: ProductDisplayCardProps) => (
98
+ <View style={styles.column}>
99
+ <ProductDisplayCard {...args} />
100
+ </View>
101
+ )
102
+ Playground.args = {
103
+ device: "mobile",
104
+ title: "Recipe Name",
105
+ subtext: "+£0.00/pouch",
106
+ showSubtext: true,
107
+ quantity: 1,
108
+ showQuantityPicker: true,
109
+ showBanner: true,
110
+ bannerType: "info",
111
+ showBannerIcon: false,
112
+ banner: " High in protein • Natural ingredients • Vet approved",
113
+ image: placeholderImageUrl
114
+ }
115
+
116
+ export const Default = () => (
117
+ <View style={styles.column}>
118
+ <ProductDisplayCard
119
+ device="mobile"
120
+ title="Recipe Name"
121
+ subtext="+£0.00/pouch"
122
+ showSubtext
123
+ quantity={1}
124
+ showQuantityPicker={false}
125
+ showBanner={false}
126
+ image={placeholderImageUrl}
127
+ />
128
+ </View>
129
+ )
130
+
131
+ export const Desktop = () => (
132
+ <View style={styles.column}>
133
+ <ProductDisplayCard
134
+ device="desktop"
135
+ title="Recipe Name"
136
+ subtext="+£0.00/pouch"
137
+ showSubtext
138
+ quantity={1}
139
+ showQuantityPicker
140
+ showBanner={false}
141
+ image={placeholderImageUrl}
142
+ />
143
+ </View>
144
+ )
145
+
146
+ export const Mobile = () => (
147
+ <View style={styles.column}>
148
+ <ProductDisplayCard
149
+ device="mobile"
150
+ title="Recipe Name"
151
+ subtext="+£0.50/pouch"
152
+ showSubtext
153
+ quantity={1}
154
+ showQuantityPicker
155
+ showBanner={false}
156
+ image={placeholderImageUrl}
157
+ />
158
+ </View>
159
+ )
160
+
161
+ export const WithBanner = () => (
162
+ <View style={styles.column}>
163
+ <ProductDisplayCard
164
+ device="mobile"
165
+ title="Recipe Name"
166
+ subtext="+£0.00/pouch"
167
+ showSubtext
168
+ quantity={1}
169
+ showQuantityPicker
170
+ showBanner
171
+ bannerType="success"
172
+ showBannerIcon={false}
173
+ banner="High in protein • Natural ingredients • Vet approved"
174
+ image={placeholderImageUrl}
175
+ />
176
+ </View>
177
+ )
178
+
179
+ export const WithBannerIcon = () => (
180
+ <View style={styles.column}>
181
+ <ProductDisplayCard
182
+ device="mobile"
183
+ title="Recipe Name"
184
+ subtext="+£0.00/pouch"
185
+ showSubtext
186
+ quantity={1}
187
+ showQuantityPicker
188
+ showBanner
189
+ bannerType="success"
190
+ showBannerIcon
191
+ banner="High in protein • Natural ingredients • Vet approved"
192
+ image={placeholderImageUrl}
193
+ />
194
+ </View>
195
+ )
196
+
197
+ export const NoSubtext = () => (
198
+ <View style={styles.column}>
199
+ <ProductDisplayCard
200
+ device="mobile"
201
+ title="Recipe Name"
202
+ showSubtext={false}
203
+ quantity={1}
204
+ showQuantityPicker
205
+ showBanner={false}
206
+ image={placeholderImageUrl}
207
+ />
208
+ </View>
209
+ )
210
+
211
+ export const WithImageQuantityBadge = () => (
212
+ <View style={styles.column}>
213
+ <ProductDisplayCard
214
+ device="mobile"
215
+ title="Gobble Gobble Turkey"
216
+ subtext="+£0.50/pouch"
217
+ showSubtext
218
+ quantity={4}
219
+ showQuantityPicker={false}
220
+ showImageQuantityBadge
221
+ showBanner={false}
222
+ image={placeholderImageUrl}
223
+ />
224
+
225
+ <ProductDisplayCard
226
+ device="mobile"
227
+ title="Salmon Delight"
228
+ subtext="+£0.25/pouch"
229
+ showSubtext
230
+ quantity={2}
231
+ showQuantityPicker
232
+ showImageQuantityBadge
233
+ showBanner={false}
234
+ image={placeholderImageUrl}
235
+ />
236
+ </View>
237
+ )
238
+
239
+ export const AllVariants = () => (
240
+ <View style={styles.column}>
241
+ <ProductDisplayCard
242
+ device="desktop"
243
+ title="Recipe Name"
244
+ subtext="+£0.00/pouch"
245
+ showSubtext
246
+ quantity={1}
247
+ showQuantityPicker
248
+ showBanner={false}
249
+ image={placeholderImageUrl}
250
+ />
251
+
252
+ <ProductDisplayCard
253
+ device="mobile"
254
+ title="Recipe Name"
255
+ subtext="+£0.50/pouch"
256
+ showSubtext
257
+ quantity={2}
258
+ showQuantityPicker
259
+ showBanner={false}
260
+ image={placeholderImageUrl}
261
+ />
262
+
263
+ <ProductDisplayCard
264
+ device="mobile"
265
+ title="Recipe Name"
266
+ subtext="+£0.00/pouch"
267
+ showSubtext
268
+ quantity={1}
269
+ showQuantityPicker
270
+ showBanner
271
+ banner=" High in protein • Natural ingredients"
272
+ image={placeholderImageUrl}
273
+ />
274
+ </View>
275
+ )
@@ -0,0 +1,198 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { screen } from "@testing-library/react"
3
+ import { View, Text } from "react-native"
4
+ import { ProductDisplayCard } from "./ProductDisplayCard"
5
+ import { renderWithTheme } from "../../../test-utils"
6
+
7
+ describe("ProductDisplayCard (Native)", () => {
8
+ it("renders with required title prop", () => {
9
+ renderWithTheme(<ProductDisplayCard title="Test Product" />)
10
+
11
+ expect(screen.getByText("Test Product")).toBeTruthy()
12
+ })
13
+
14
+ it("renders subtext when provided and showSubtext is true", () => {
15
+ renderWithTheme(
16
+ <ProductDisplayCard
17
+ title="Test Product"
18
+ subtext="£9.99"
19
+ showSubtext={true}
20
+ />
21
+ )
22
+
23
+ expect(screen.getByText("£9.99")).toBeTruthy()
24
+ })
25
+
26
+ it("hides subtext when showSubtext is false", () => {
27
+ renderWithTheme(
28
+ <ProductDisplayCard
29
+ title="Test Product"
30
+ subtext="£9.99"
31
+ showSubtext={false}
32
+ />
33
+ )
34
+
35
+ expect(screen.queryByText("£9.99")).toBeFalsy()
36
+ })
37
+
38
+ it("renders image when provided", () => {
39
+ renderWithTheme(
40
+ <ProductDisplayCard
41
+ title="Test Product"
42
+ image={
43
+ <View>
44
+ <Text>Image</Text>
45
+ </View>
46
+ }
47
+ />
48
+ )
49
+
50
+ expect(screen.getByText("Image")).toBeTruthy()
51
+ })
52
+
53
+ it("shows quantity picker when showQuantityPicker is true", () => {
54
+ renderWithTheme(
55
+ <ProductDisplayCard
56
+ title="Test Product"
57
+ showQuantityPicker={true}
58
+ quantity={1}
59
+ />
60
+ )
61
+
62
+ const numberInput = screen.getByDisplayValue("1")
63
+ expect(numberInput).toBeTruthy()
64
+ })
65
+
66
+ it("does not show quantity picker when showQuantityPicker is false", () => {
67
+ renderWithTheme(
68
+ <ProductDisplayCard title="Test Product" showQuantityPicker={false} />
69
+ )
70
+
71
+ expect(screen.queryByDisplayValue("1")).toBeFalsy()
72
+ })
73
+
74
+ it("renders banner when showBanner is true", () => {
75
+ renderWithTheme(
76
+ <ProductDisplayCard
77
+ title="Test Product"
78
+ showBanner={true}
79
+ banner={<Text>Test Banner</Text>}
80
+ />
81
+ )
82
+
83
+ expect(screen.getByText("Test Banner")).toBeTruthy()
84
+ })
85
+
86
+ it("does not render banner when showBanner is false", () => {
87
+ renderWithTheme(
88
+ <ProductDisplayCard
89
+ title="Test Product"
90
+ showBanner={false}
91
+ banner={<Text>Test Banner</Text>}
92
+ />
93
+ )
94
+
95
+ expect(screen.queryByText("Test Banner")).toBeFalsy()
96
+ })
97
+
98
+ it("renders with desktop device variant by default", () => {
99
+ renderWithTheme(<ProductDisplayCard title="Test Product" />)
100
+
101
+ expect(screen.getByText("Test Product")).toBeTruthy()
102
+ })
103
+
104
+ it("renders with mobile device variant when specified", () => {
105
+ renderWithTheme(<ProductDisplayCard title="Test Product" device="mobile" />)
106
+
107
+ expect(screen.getByText("Test Product")).toBeTruthy()
108
+ })
109
+
110
+ it("forwards ref correctly", () => {
111
+ const ref = { current: null as any }
112
+
113
+ renderWithTheme(<ProductDisplayCard ref={ref} title="Test Product" />)
114
+
115
+ expect(ref.current).toBeTruthy()
116
+ })
117
+
118
+ it("renders title and subtext together", () => {
119
+ renderWithTheme(
120
+ <ProductDisplayCard
121
+ title="Premium Recipe"
122
+ subtext="+£0.00/pouch"
123
+ showSubtext={true}
124
+ />
125
+ )
126
+
127
+ expect(screen.getByText("Premium Recipe")).toBeTruthy()
128
+ expect(screen.getByText("+£0.00/pouch")).toBeTruthy()
129
+ })
130
+
131
+ it("accepts React element as image", () => {
132
+ renderWithTheme(
133
+ <ProductDisplayCard
134
+ title="Test Product"
135
+ image={
136
+ <View>
137
+ <Text>Custom Image</Text>
138
+ </View>
139
+ }
140
+ />
141
+ )
142
+
143
+ expect(screen.getByText("Custom Image")).toBeTruthy()
144
+ })
145
+
146
+ it("accepts React element as banner", () => {
147
+ renderWithTheme(
148
+ <ProductDisplayCard
149
+ title="Test Product"
150
+ showBanner={true}
151
+ banner={
152
+ <View>
153
+ <Text>Custom Banner</Text>
154
+ </View>
155
+ }
156
+ />
157
+ )
158
+
159
+ expect(screen.getByText("Custom Banner")).toBeTruthy()
160
+ })
161
+
162
+ it("renders banner with different types", () => {
163
+ const { rerender } = renderWithTheme(
164
+ <ProductDisplayCard
165
+ title="Test Product"
166
+ showBanner={true}
167
+ banner={<Text>Test Banner</Text>}
168
+ bannerType="success"
169
+ />
170
+ )
171
+
172
+ expect(screen.getByText("Test Banner")).toBeTruthy()
173
+
174
+ rerender(
175
+ <ProductDisplayCard
176
+ title="Test Product"
177
+ showBanner={true}
178
+ banner={<Text>Error Banner</Text>}
179
+ bannerType="error"
180
+ />
181
+ )
182
+
183
+ expect(screen.getByText("Error Banner")).toBeTruthy()
184
+ })
185
+
186
+ it("displays banner with icon when showBannerIcon is true", () => {
187
+ renderWithTheme(
188
+ <ProductDisplayCard
189
+ title="Test Product"
190
+ showBanner={true}
191
+ banner={<Text>Test Banner</Text>}
192
+ showBannerIcon={true}
193
+ />
194
+ )
195
+
196
+ expect(screen.getByText("Test Banner")).toBeTruthy()
197
+ })
198
+ })