@butternutbox/pawprint-native 0.4.1 → 0.5.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/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +18 -0
- package/dist/index.cjs +102 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -10
- package/dist/index.d.ts +34 -10
- package/dist/index.js +102 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/molecules/Countdown/Countdown.stories.tsx +35 -0
- package/src/components/molecules/Countdown/Countdown.tsx +52 -10
- package/src/components/molecules/Countdown/index.ts +5 -1
- package/src/components/molecules/Drawer/DrawerBody.tsx +29 -7
- package/src/components/molecules/ProductDisplayCard/ProductDisplayCard.stories.tsx +25 -11
- package/src/components/molecules/ProductDisplayCard/ProductDisplayCard.test.tsx +1 -1
- package/src/components/molecules/ProductDisplayCard/ProductDisplayCard.tsx +53 -33
- package/src/components/molecules/ProductDisplayCard/index.ts +1 -4
package/package.json
CHANGED
|
@@ -40,6 +40,11 @@ export default {
|
|
|
40
40
|
label: {
|
|
41
41
|
control: { type: "text" },
|
|
42
42
|
description: "Customizable prefix label (inline layout only)"
|
|
43
|
+
},
|
|
44
|
+
labels: {
|
|
45
|
+
control: { type: "object" },
|
|
46
|
+
description:
|
|
47
|
+
"Per-unit labels (days/hours/minutes/seconds). Pass translated strings; omitted units fall back to the English defaults."
|
|
43
48
|
}
|
|
44
49
|
}
|
|
45
50
|
}
|
|
@@ -139,6 +144,36 @@ export const WithoutSeconds = {
|
|
|
139
144
|
)
|
|
140
145
|
}
|
|
141
146
|
|
|
147
|
+
export const TranslatedLabels = {
|
|
148
|
+
name: "Translated Labels",
|
|
149
|
+
render: () => (
|
|
150
|
+
<View style={styles.column}>
|
|
151
|
+
<Countdown
|
|
152
|
+
layout="stacked"
|
|
153
|
+
labels={{
|
|
154
|
+
days: { singular: "jour", plural: "jours" },
|
|
155
|
+
hours: { singular: "heure", plural: "heures" },
|
|
156
|
+
minutes: { singular: "min", plural: "min" },
|
|
157
|
+
seconds: { singular: "sec", plural: "sec" }
|
|
158
|
+
}}
|
|
159
|
+
days={1}
|
|
160
|
+
hours={2}
|
|
161
|
+
minutes={48}
|
|
162
|
+
seconds={38}
|
|
163
|
+
/>
|
|
164
|
+
<Countdown
|
|
165
|
+
layout="inline"
|
|
166
|
+
label="La vente se termine dans :"
|
|
167
|
+
labels={{ days: "j", hours: "h", minutes: "min", seconds: "s" }}
|
|
168
|
+
days={2}
|
|
169
|
+
hours={9}
|
|
170
|
+
minutes={48}
|
|
171
|
+
seconds={38}
|
|
172
|
+
/>
|
|
173
|
+
</View>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
142
177
|
export const AllVariants = {
|
|
143
178
|
name: "All Variants",
|
|
144
179
|
render: () => (
|
|
@@ -12,6 +12,20 @@ type CountdownUnit = {
|
|
|
12
12
|
label: string
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* A unit label. Pass a plain string to use it regardless of count, or a
|
|
17
|
+
* `{ singular, plural }` pair to pluralise based on the unit's value
|
|
18
|
+
* (`singular` when the value is exactly 1, `plural` otherwise).
|
|
19
|
+
*/
|
|
20
|
+
export type CountdownUnitLabel = string | { singular: string; plural: string }
|
|
21
|
+
|
|
22
|
+
export type CountdownLabels = {
|
|
23
|
+
days?: CountdownUnitLabel
|
|
24
|
+
hours?: CountdownUnitLabel
|
|
25
|
+
minutes?: CountdownUnitLabel
|
|
26
|
+
seconds?: CountdownUnitLabel
|
|
27
|
+
}
|
|
28
|
+
|
|
15
29
|
type CountdownOwnProps = {
|
|
16
30
|
layout?: CountdownLayout
|
|
17
31
|
days?: number
|
|
@@ -21,17 +35,25 @@ type CountdownOwnProps = {
|
|
|
21
35
|
showDays?: boolean
|
|
22
36
|
showSeconds?: boolean
|
|
23
37
|
label?: React.ReactNode
|
|
38
|
+
labels?: CountdownLabels
|
|
24
39
|
}
|
|
25
40
|
|
|
26
41
|
export type CountdownProps = CountdownOwnProps &
|
|
27
42
|
Omit<ViewProps, keyof CountdownOwnProps>
|
|
28
43
|
|
|
29
|
-
const
|
|
30
|
-
days: "days",
|
|
31
|
-
hours: "hrs",
|
|
32
|
-
minutes: "min",
|
|
33
|
-
seconds: "secs"
|
|
34
|
-
}
|
|
44
|
+
const DEFAULT_UNIT_LABELS: Required<CountdownLabels> = {
|
|
45
|
+
days: { singular: "day", plural: "days" },
|
|
46
|
+
hours: { singular: "hr", plural: "hrs" },
|
|
47
|
+
minutes: { singular: "min", plural: "min" },
|
|
48
|
+
seconds: { singular: "sec", plural: "secs" }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const resolveLabel = (label: CountdownUnitLabel, value: number): string =>
|
|
52
|
+
typeof label === "string"
|
|
53
|
+
? label
|
|
54
|
+
: value === 1
|
|
55
|
+
? label.singular
|
|
56
|
+
: label.plural
|
|
35
57
|
|
|
36
58
|
const parseTokenValue = (value: string): number => parseFloat(value)
|
|
37
59
|
|
|
@@ -154,6 +176,7 @@ const InlineCount = styled(View)<{ countGap: number }>(({ countGap }) => ({
|
|
|
154
176
|
* @param {boolean} [showDays=true] - Whether to render the days unit and its separator.
|
|
155
177
|
* @param {boolean} [showSeconds=true] - Whether to render the seconds unit and its separator.
|
|
156
178
|
* @param {React.ReactNode} [label] - Customizable prefix label, rendered in the `inline` layout only.
|
|
179
|
+
* @param {CountdownLabels} [labels] - Overrides the per-unit labels (`days`/`hours`/`minutes`/`seconds`). Each label is either a plain string or a `{ singular, plural }` pair that resolves against the unit's value. Pass translated strings here; any omitted unit falls back to its English default.
|
|
157
180
|
*
|
|
158
181
|
* @example
|
|
159
182
|
* ```tsx
|
|
@@ -161,6 +184,14 @@ const InlineCount = styled(View)<{ countGap: number }>(({ countGap }) => ({
|
|
|
161
184
|
*
|
|
162
185
|
* <Countdown layout="stacked" days={2} hours={9} minutes={48} seconds={38} />
|
|
163
186
|
* <Countdown layout="inline" label="Sale ends:" showDays={false} hours={3} minutes={49} seconds={38} />
|
|
187
|
+
* <Countdown
|
|
188
|
+
* labels={{
|
|
189
|
+
* days: { singular: "jour", plural: "jours" },
|
|
190
|
+
* hours: { singular: "heure", plural: "heures" }
|
|
191
|
+
* }}
|
|
192
|
+
* days={1}
|
|
193
|
+
* hours={2}
|
|
194
|
+
* />
|
|
164
195
|
* ```
|
|
165
196
|
*/
|
|
166
197
|
export const Countdown = React.forwardRef<View, CountdownProps>(
|
|
@@ -174,6 +205,7 @@ export const Countdown = React.forwardRef<View, CountdownProps>(
|
|
|
174
205
|
showDays = true,
|
|
175
206
|
showSeconds = true,
|
|
176
207
|
label,
|
|
208
|
+
labels,
|
|
177
209
|
...rest
|
|
178
210
|
},
|
|
179
211
|
ref
|
|
@@ -182,18 +214,28 @@ export const Countdown = React.forwardRef<View, CountdownProps>(
|
|
|
182
214
|
const { countdown } = theme.tokens.components
|
|
183
215
|
const { countdownItem } = countdown
|
|
184
216
|
|
|
217
|
+
const unitLabels = { ...DEFAULT_UNIT_LABELS, ...labels }
|
|
218
|
+
|
|
185
219
|
const units: CountdownUnit[] = [
|
|
186
220
|
showDays && {
|
|
187
221
|
key: "days" as const,
|
|
188
222
|
value: days,
|
|
189
|
-
label:
|
|
223
|
+
label: resolveLabel(unitLabels.days, days)
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
key: "hours" as const,
|
|
227
|
+
value: hours,
|
|
228
|
+
label: resolveLabel(unitLabels.hours, hours)
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
key: "minutes" as const,
|
|
232
|
+
value: minutes,
|
|
233
|
+
label: resolveLabel(unitLabels.minutes, minutes)
|
|
190
234
|
},
|
|
191
|
-
{ key: "hours" as const, value: hours, label: UNIT_LABELS.hours },
|
|
192
|
-
{ key: "minutes" as const, value: minutes, label: UNIT_LABELS.minutes },
|
|
193
235
|
showSeconds && {
|
|
194
236
|
key: "seconds" as const,
|
|
195
237
|
value: seconds,
|
|
196
|
-
label:
|
|
238
|
+
label: resolveLabel(unitLabels.seconds, seconds)
|
|
197
239
|
}
|
|
198
240
|
].filter(Boolean) as CountdownUnit[]
|
|
199
241
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React from "react"
|
|
2
|
-
import { ScrollView, ScrollViewProps } from "react-native"
|
|
2
|
+
import { ScrollView, ScrollViewProps, useWindowDimensions } from "react-native"
|
|
3
3
|
import styled from "@emotion/native"
|
|
4
4
|
import { useTheme } from "@emotion/react"
|
|
5
5
|
import { useDrawerHeaderContext } from "./DrawerHeaderContext"
|
|
6
|
+
import { useDrawerFooterContext } from "./DrawerFooterContext"
|
|
6
7
|
|
|
7
8
|
export type DrawerBodyProps = ScrollViewProps
|
|
8
9
|
|
|
@@ -12,12 +13,24 @@ const StyledScrollView = styled(ScrollView)<{
|
|
|
12
13
|
bodyPaddingHorizontal: number
|
|
13
14
|
bodyPaddingRight: number
|
|
14
15
|
bodyGap: number
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
bodyMaxHeight: number
|
|
17
|
+
bodyPaddingBottom: number
|
|
18
|
+
}>(
|
|
19
|
+
({
|
|
20
|
+
bodyPaddingHorizontal,
|
|
21
|
+
bodyPaddingRight,
|
|
22
|
+
bodyGap,
|
|
23
|
+
bodyMaxHeight,
|
|
24
|
+
bodyPaddingBottom
|
|
25
|
+
}) => ({
|
|
26
|
+
flex: 1,
|
|
27
|
+
paddingLeft: bodyPaddingHorizontal,
|
|
28
|
+
paddingRight: bodyPaddingRight,
|
|
29
|
+
gap: bodyGap,
|
|
30
|
+
maxHeight: bodyMaxHeight,
|
|
31
|
+
paddingBottom: bodyPaddingBottom
|
|
32
|
+
})
|
|
33
|
+
)
|
|
21
34
|
|
|
22
35
|
/**
|
|
23
36
|
* Scrollable content area of the drawer. Grows to fill available space
|
|
@@ -30,10 +43,13 @@ export const DrawerBody = React.forwardRef<ScrollView, DrawerBodyProps>(
|
|
|
30
43
|
const { buttons } = theme.tokens.components
|
|
31
44
|
const { content } = spacing
|
|
32
45
|
const headerContext = useDrawerHeaderContext()
|
|
46
|
+
const footerContext = useDrawerFooterContext()
|
|
47
|
+
const { height: windowHeight } = useWindowDimensions()
|
|
33
48
|
|
|
34
49
|
const gap = parseTokenValue(content.slot.gap)
|
|
35
50
|
const horizontalPadding = parseTokenValue(content.slot.horizontalPadding)
|
|
36
51
|
const topPadding = parseTokenValue(content.slot.verticalPadding)
|
|
52
|
+
const bodyMaxHeight = windowHeight - 300
|
|
37
53
|
|
|
38
54
|
// When there's no header the close button floats in the top-right corner.
|
|
39
55
|
// Reserve space on the right so body content doesn't slide under it.
|
|
@@ -43,12 +59,18 @@ export const DrawerBody = React.forwardRef<ScrollView, DrawerBodyProps>(
|
|
|
43
59
|
parseTokenValue(buttons.size.sm.height)
|
|
44
60
|
: horizontalPadding
|
|
45
61
|
|
|
62
|
+
const bodyPaddingBottom = !footerContext
|
|
63
|
+
? parseTokenValue(theme.tokens.semantics.dimensions.spacing["2xl"])
|
|
64
|
+
: 0
|
|
65
|
+
|
|
46
66
|
return (
|
|
47
67
|
<StyledScrollView
|
|
48
68
|
ref={ref}
|
|
49
69
|
bodyPaddingHorizontal={horizontalPadding}
|
|
50
70
|
bodyPaddingRight={paddingRight}
|
|
51
71
|
bodyGap={gap}
|
|
72
|
+
bodyMaxHeight={bodyMaxHeight}
|
|
73
|
+
bodyPaddingBottom={bodyPaddingBottom}
|
|
52
74
|
contentContainerStyle={[
|
|
53
75
|
{
|
|
54
76
|
gap,
|
|
@@ -116,7 +116,6 @@ Playground.args = {
|
|
|
116
116
|
export const Default = () => (
|
|
117
117
|
<View style={styles.column}>
|
|
118
118
|
<ProductDisplayCard
|
|
119
|
-
device="mobile"
|
|
120
119
|
title="Recipe Name"
|
|
121
120
|
subtext="+£0.00/pouch"
|
|
122
121
|
showSubtext
|
|
@@ -131,7 +130,6 @@ export const Default = () => (
|
|
|
131
130
|
export const Desktop = () => (
|
|
132
131
|
<View style={styles.column}>
|
|
133
132
|
<ProductDisplayCard
|
|
134
|
-
device="desktop"
|
|
135
133
|
title="Recipe Name"
|
|
136
134
|
subtext="+£0.00/pouch"
|
|
137
135
|
showSubtext
|
|
@@ -146,7 +144,6 @@ export const Desktop = () => (
|
|
|
146
144
|
export const Mobile = () => (
|
|
147
145
|
<View style={styles.column}>
|
|
148
146
|
<ProductDisplayCard
|
|
149
|
-
device="mobile"
|
|
150
147
|
title="Recipe Name"
|
|
151
148
|
subtext="+£0.50/pouch"
|
|
152
149
|
showSubtext
|
|
@@ -161,7 +158,6 @@ export const Mobile = () => (
|
|
|
161
158
|
export const WithBanner = () => (
|
|
162
159
|
<View style={styles.column}>
|
|
163
160
|
<ProductDisplayCard
|
|
164
|
-
device="mobile"
|
|
165
161
|
title="Recipe Name"
|
|
166
162
|
subtext="+£0.00/pouch"
|
|
167
163
|
showSubtext
|
|
@@ -179,7 +175,6 @@ export const WithBanner = () => (
|
|
|
179
175
|
export const WithBannerIcon = () => (
|
|
180
176
|
<View style={styles.column}>
|
|
181
177
|
<ProductDisplayCard
|
|
182
|
-
device="mobile"
|
|
183
178
|
title="Recipe Name"
|
|
184
179
|
subtext="+£0.00/pouch"
|
|
185
180
|
showSubtext
|
|
@@ -197,7 +192,6 @@ export const WithBannerIcon = () => (
|
|
|
197
192
|
export const NoSubtext = () => (
|
|
198
193
|
<View style={styles.column}>
|
|
199
194
|
<ProductDisplayCard
|
|
200
|
-
device="mobile"
|
|
201
195
|
title="Recipe Name"
|
|
202
196
|
showSubtext={false}
|
|
203
197
|
quantity={1}
|
|
@@ -211,7 +205,6 @@ export const NoSubtext = () => (
|
|
|
211
205
|
export const WithImageQuantityBadge = () => (
|
|
212
206
|
<View style={styles.column}>
|
|
213
207
|
<ProductDisplayCard
|
|
214
|
-
device="mobile"
|
|
215
208
|
title="Gobble Gobble Turkey"
|
|
216
209
|
subtext="+£0.50/pouch"
|
|
217
210
|
showSubtext
|
|
@@ -223,7 +216,6 @@ export const WithImageQuantityBadge = () => (
|
|
|
223
216
|
/>
|
|
224
217
|
|
|
225
218
|
<ProductDisplayCard
|
|
226
|
-
device="mobile"
|
|
227
219
|
title="Salmon Delight"
|
|
228
220
|
subtext="+£0.25/pouch"
|
|
229
221
|
showSubtext
|
|
@@ -239,7 +231,6 @@ export const WithImageQuantityBadge = () => (
|
|
|
239
231
|
export const AllVariants = () => (
|
|
240
232
|
<View style={styles.column}>
|
|
241
233
|
<ProductDisplayCard
|
|
242
|
-
device="desktop"
|
|
243
234
|
title="Recipe Name"
|
|
244
235
|
subtext="+£0.00/pouch"
|
|
245
236
|
showSubtext
|
|
@@ -250,7 +241,6 @@ export const AllVariants = () => (
|
|
|
250
241
|
/>
|
|
251
242
|
|
|
252
243
|
<ProductDisplayCard
|
|
253
|
-
device="mobile"
|
|
254
244
|
title="Recipe Name"
|
|
255
245
|
subtext="+£0.50/pouch"
|
|
256
246
|
showSubtext
|
|
@@ -261,7 +251,6 @@ export const AllVariants = () => (
|
|
|
261
251
|
/>
|
|
262
252
|
|
|
263
253
|
<ProductDisplayCard
|
|
264
|
-
device="mobile"
|
|
265
254
|
title="Recipe Name"
|
|
266
255
|
subtext="+£0.00/pouch"
|
|
267
256
|
showSubtext
|
|
@@ -273,3 +262,28 @@ export const AllVariants = () => (
|
|
|
273
262
|
/>
|
|
274
263
|
</View>
|
|
275
264
|
)
|
|
265
|
+
|
|
266
|
+
export const Borderless = () => (
|
|
267
|
+
<View style={styles.column}>
|
|
268
|
+
<ProductDisplayCard
|
|
269
|
+
title="Recipe Name"
|
|
270
|
+
subtext="+£0.00/pouch"
|
|
271
|
+
showSubtext
|
|
272
|
+
quantity={1}
|
|
273
|
+
showQuantityPicker
|
|
274
|
+
borderless
|
|
275
|
+
image={placeholderImageUrl}
|
|
276
|
+
/>
|
|
277
|
+
|
|
278
|
+
<ProductDisplayCard
|
|
279
|
+
title="Premium Recipe"
|
|
280
|
+
subtext="+£0.50/pouch"
|
|
281
|
+
showSubtext
|
|
282
|
+
quantity={2}
|
|
283
|
+
showQuantityPicker
|
|
284
|
+
showImageQuantityBadge
|
|
285
|
+
borderless
|
|
286
|
+
image={placeholderImageUrl}
|
|
287
|
+
/>
|
|
288
|
+
</View>
|
|
289
|
+
)
|
|
@@ -102,7 +102,7 @@ describe("ProductDisplayCard (Native)", () => {
|
|
|
102
102
|
})
|
|
103
103
|
|
|
104
104
|
it("renders with mobile device variant when specified", () => {
|
|
105
|
-
renderWithTheme(<ProductDisplayCard title="Test Product"
|
|
105
|
+
renderWithTheme(<ProductDisplayCard title="Test Product" />)
|
|
106
106
|
|
|
107
107
|
expect(screen.getByText("Test Product")).toBeTruthy()
|
|
108
108
|
})
|
|
@@ -6,10 +6,7 @@ import { NumberField } from "../../molecules/NumberField"
|
|
|
6
6
|
import { Notification } from "../Notification"
|
|
7
7
|
import { parseTokenValue } from "../../../utils"
|
|
8
8
|
|
|
9
|
-
export type ProductDisplayCardDevice = "desktop" | "mobile"
|
|
10
|
-
|
|
11
9
|
export type ProductDisplayCardProps = ViewProps & {
|
|
12
|
-
device?: ProductDisplayCardDevice
|
|
13
10
|
title: string
|
|
14
11
|
subtext?: string | React.ReactNode
|
|
15
12
|
showSubtext?: boolean
|
|
@@ -24,16 +21,20 @@ export type ProductDisplayCardProps = ViewProps & {
|
|
|
24
21
|
decrementDisabled?: boolean
|
|
25
22
|
image?: string | React.ReactNode
|
|
26
23
|
imageBackgroundColor?: string
|
|
24
|
+
thumbnailWidth?: number
|
|
25
|
+
thumbnailBackgroundColor?: string
|
|
27
26
|
showImageQuantityBadge?: boolean
|
|
27
|
+
borderless?: boolean
|
|
28
28
|
banner?: React.ReactNode
|
|
29
29
|
showBanner?: boolean
|
|
30
30
|
bannerType?: "error" | "success" | "warning" | "info"
|
|
31
31
|
showBannerIcon?: boolean
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
const StyledCardContainer = styled(View)<{
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
const StyledCardContainer = styled(View)<{ borderless?: boolean }>(({
|
|
35
|
+
theme,
|
|
36
|
+
borderless
|
|
37
|
+
}) => {
|
|
37
38
|
const { spacing, borderRadius } = theme.tokens.semantics.dimensions
|
|
38
39
|
const { colour } = theme.tokens.semantics
|
|
39
40
|
const spacingMd = parseTokenValue(spacing.md)
|
|
@@ -41,31 +42,46 @@ const StyledCardContainer = styled(View)<{
|
|
|
41
42
|
return {
|
|
42
43
|
flexDirection: "row",
|
|
43
44
|
width: "100%",
|
|
44
|
-
height: "100%",
|
|
45
|
+
height: borderless ? undefined : "100%",
|
|
45
46
|
maxHeight: spacingXl * 6 + spacingMd,
|
|
46
47
|
backgroundColor: colour.background.surface.default,
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
...(borderless
|
|
49
|
+
? {}
|
|
50
|
+
: {
|
|
51
|
+
borderWidth: 1,
|
|
52
|
+
borderColor: colour.border.default,
|
|
53
|
+
shadowColor: "#522a10",
|
|
54
|
+
shadowOffset: { width: 0, height: spacingMd / 4 },
|
|
55
|
+
shadowOpacity: 0.05,
|
|
56
|
+
shadowRadius: spacingXl - 4,
|
|
57
|
+
elevation: 5
|
|
58
|
+
}),
|
|
49
59
|
borderRadius: parseTokenValue(borderRadius.md),
|
|
50
60
|
overflow: "hidden",
|
|
51
|
-
shadowColor: "#522a10",
|
|
52
|
-
shadowOffset: { width: 0, height: spacingMd / 4 },
|
|
53
|
-
shadowOpacity: 0.05,
|
|
54
|
-
shadowRadius: spacingXl - 4,
|
|
55
|
-
elevation: 5,
|
|
56
61
|
zIndex: 1
|
|
57
62
|
}
|
|
58
63
|
})
|
|
59
64
|
|
|
60
|
-
const StyledImage = styled(View)<{
|
|
61
|
-
|
|
65
|
+
const StyledImage = styled(View)<{
|
|
66
|
+
imageBackgroundColor?: string
|
|
67
|
+
thumbnailWidth?: number
|
|
68
|
+
thumbnailBackgroundColor?: string
|
|
69
|
+
}>(
|
|
70
|
+
({
|
|
71
|
+
theme,
|
|
72
|
+
imageBackgroundColor,
|
|
73
|
+
thumbnailWidth = 100,
|
|
74
|
+
thumbnailBackgroundColor
|
|
75
|
+
}) => ({
|
|
62
76
|
flexShrink: 0,
|
|
63
77
|
backgroundColor:
|
|
78
|
+
thumbnailBackgroundColor ||
|
|
64
79
|
imageBackgroundColor ||
|
|
65
80
|
theme.tokens.semantics.colour.background.container.secondary,
|
|
66
81
|
overflow: "hidden",
|
|
67
82
|
aspectRatio: "1 / 1",
|
|
68
|
-
|
|
83
|
+
width: thumbnailWidth,
|
|
84
|
+
height: thumbnailWidth,
|
|
69
85
|
position: "relative"
|
|
70
86
|
})
|
|
71
87
|
)
|
|
@@ -90,14 +106,16 @@ const StyledQuantityBadge = styled(View)(({ theme }) => {
|
|
|
90
106
|
}
|
|
91
107
|
})
|
|
92
108
|
|
|
93
|
-
const StyledContentArea = styled(View)<{
|
|
94
|
-
|
|
95
|
-
|
|
109
|
+
const StyledContentArea = styled(View)<{ borderless?: boolean }>(({
|
|
110
|
+
theme,
|
|
111
|
+
borderless
|
|
112
|
+
}) => {
|
|
96
113
|
const { spacing } = theme.tokens.semantics.dimensions
|
|
114
|
+
const paddingValue = parseTokenValue(spacing.md)
|
|
97
115
|
return {
|
|
98
116
|
flex: 1,
|
|
99
|
-
paddingHorizontal:
|
|
100
|
-
paddingVertical:
|
|
117
|
+
paddingHorizontal: paddingValue,
|
|
118
|
+
paddingVertical: borderless ? 0 : paddingValue,
|
|
101
119
|
gap: parseTokenValue(spacing.xs),
|
|
102
120
|
justifyContent: "space-between"
|
|
103
121
|
}
|
|
@@ -139,7 +157,6 @@ const StyledNotification = styled(Notification as any)(({ theme }) => {
|
|
|
139
157
|
* import { ProductDisplayCard } from "@butternutbox/pawprint-native"
|
|
140
158
|
*
|
|
141
159
|
* <ProductDisplayCard
|
|
142
|
-
* device="mobile"
|
|
143
160
|
* title="Premium Recipe"
|
|
144
161
|
* subtext="+£0.00/pouch"
|
|
145
162
|
* quantity={1}
|
|
@@ -147,16 +164,13 @@ const StyledNotification = styled(Notification as any)(({ theme }) => {
|
|
|
147
164
|
* />
|
|
148
165
|
* ```
|
|
149
166
|
*
|
|
150
|
-
* @param device - *(optional)* Device variant: "desktop" or "mobile" (default)
|
|
151
167
|
* @param title - Product title
|
|
152
|
-
* @param subtext - *(optional)* Subtitle text
|
|
168
|
+
* @param subtext - *(optional)* Subtitle text (e.g. price)
|
|
153
169
|
* @param showSubtext - *(optional)* Show/hide subtext
|
|
154
170
|
* @param quantity - *(optional)* Current quantity value
|
|
155
171
|
* @param onQuantityChange - *(optional)* Callback when quantity changes
|
|
156
172
|
* @param showQuantityPicker - *(optional)* Show/hide quantity picker
|
|
157
|
-
* @param image - *(optional)* Image
|
|
158
|
-
* @param imageBackgroundColor - *(optional)* Background color for image container (default: theme background.container.secondary)
|
|
159
|
-
* @param showImageQuantityBadge - *(optional)* Show/hide quantity badge overlay on image
|
|
173
|
+
* @param image - *(optional)* Image element or content
|
|
160
174
|
* @param banner - *(optional)* Banner content to show below card
|
|
161
175
|
* @param showBanner - *(optional)* Show/hide banner
|
|
162
176
|
* @param bannerType - *(optional)* Banner notification type: "error", "success", "warning", or "info" (default)
|
|
@@ -168,7 +182,6 @@ export const ProductDisplayCard = React.forwardRef<
|
|
|
168
182
|
>(
|
|
169
183
|
(
|
|
170
184
|
{
|
|
171
|
-
device = "mobile",
|
|
172
185
|
title,
|
|
173
186
|
subtext,
|
|
174
187
|
showSubtext = true,
|
|
@@ -183,7 +196,10 @@ export const ProductDisplayCard = React.forwardRef<
|
|
|
183
196
|
decrementDisabled = false,
|
|
184
197
|
image,
|
|
185
198
|
imageBackgroundColor,
|
|
199
|
+
thumbnailWidth,
|
|
200
|
+
thumbnailBackgroundColor,
|
|
186
201
|
showImageQuantityBadge = false,
|
|
202
|
+
borderless = false,
|
|
187
203
|
banner,
|
|
188
204
|
showBanner = false,
|
|
189
205
|
bannerType = "info",
|
|
@@ -199,9 +215,13 @@ export const ProductDisplayCard = React.forwardRef<
|
|
|
199
215
|
}
|
|
200
216
|
|
|
201
217
|
const cardContent = (
|
|
202
|
-
<StyledCardContainer ref={ref}
|
|
218
|
+
<StyledCardContainer ref={ref} borderless={borderless} {...rest}>
|
|
203
219
|
{image && (
|
|
204
|
-
<StyledImage
|
|
220
|
+
<StyledImage
|
|
221
|
+
imageBackgroundColor={imageBackgroundColor}
|
|
222
|
+
thumbnailWidth={thumbnailWidth}
|
|
223
|
+
thumbnailBackgroundColor={thumbnailBackgroundColor}
|
|
224
|
+
>
|
|
205
225
|
{typeof image === "string" ? (
|
|
206
226
|
<Image
|
|
207
227
|
source={{ uri: image }}
|
|
@@ -220,9 +240,9 @@ export const ProductDisplayCard = React.forwardRef<
|
|
|
220
240
|
</StyledImage>
|
|
221
241
|
)}
|
|
222
242
|
|
|
223
|
-
<StyledContentArea
|
|
243
|
+
<StyledContentArea borderless={borderless}>
|
|
224
244
|
<StyledTextContainer>
|
|
225
|
-
<Typography variant="heading" size="
|
|
245
|
+
<Typography variant="heading" size="2xs" color="primary">
|
|
226
246
|
{title}
|
|
227
247
|
</Typography>
|
|
228
248
|
|