@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.
- package/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +14 -0
- package/dist/index.cjs +1012 -173
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +252 -5
- package/dist/index.d.ts +252 -5
- package/dist/index.js +1007 -173
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/components/atoms/Hint/Hint.tsx +1 -2
- package/src/components/atoms/Input/InputField.tsx +7 -1
- package/src/components/molecules/Animated/Animated.tsx +12 -3
- package/src/components/molecules/Countdown/Countdown.stories.tsx +218 -0
- package/src/components/molecules/Countdown/Countdown.tsx +315 -0
- package/src/components/molecules/Countdown/index.ts +2 -0
- package/src/components/molecules/NumberField/NumberFieldInput.tsx +14 -24
- package/src/components/molecules/ProductDisplayCard/ProductDisplayCard.stories.tsx +275 -0
- package/src/components/molecules/ProductDisplayCard/ProductDisplayCard.test.tsx +198 -0
- package/src/components/molecules/ProductDisplayCard/ProductDisplayCard.tsx +292 -0
- package/src/components/molecules/ProductDisplayCard/index.ts +5 -0
- package/src/components/molecules/ProductListingCard/Badge.tsx +65 -0
- package/src/components/molecules/ProductListingCard/Grid.tsx +59 -0
- package/src/components/molecules/ProductListingCard/ProductListingCard.stories.tsx +209 -0
- package/src/components/molecules/ProductListingCard/ProductListingCard.tsx +235 -0
- package/src/components/molecules/ProductListingCard/index.ts +2 -0
- package/src/components/molecules/TabNavigation/TabNavigation.stories.tsx +183 -0
- package/src/components/molecules/TabNavigation/TabNavigation.tsx +354 -0
- package/src/components/molecules/TabNavigation/index.ts +7 -0
- package/src/components/molecules/index.ts +4 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/token.ts +1 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { View, ViewProps, Image } from "react-native"
|
|
3
|
+
import styled from "@emotion/native"
|
|
4
|
+
import { Typography } from "../../atoms/Typography"
|
|
5
|
+
import { NumberField } from "../../molecules/NumberField"
|
|
6
|
+
import { Notification } from "../Notification"
|
|
7
|
+
import { parseTokenValue } from "../../../utils"
|
|
8
|
+
|
|
9
|
+
export type ProductDisplayCardDevice = "desktop" | "mobile"
|
|
10
|
+
|
|
11
|
+
export type ProductDisplayCardProps = ViewProps & {
|
|
12
|
+
device?: ProductDisplayCardDevice
|
|
13
|
+
title: string
|
|
14
|
+
subtext?: string | React.ReactNode
|
|
15
|
+
showSubtext?: boolean
|
|
16
|
+
quantity?: number
|
|
17
|
+
onQuantityChange?: (quantity: number) => void
|
|
18
|
+
showQuantityPicker?: boolean
|
|
19
|
+
disableQuantityButtons?: boolean
|
|
20
|
+
hideQuantityButtons?: boolean
|
|
21
|
+
showIncrementButton?: boolean
|
|
22
|
+
showDecrementButton?: boolean
|
|
23
|
+
incrementDisabled?: boolean
|
|
24
|
+
decrementDisabled?: boolean
|
|
25
|
+
image?: string | React.ReactNode
|
|
26
|
+
imageBackgroundColor?: string
|
|
27
|
+
showImageQuantityBadge?: boolean
|
|
28
|
+
banner?: React.ReactNode
|
|
29
|
+
showBanner?: boolean
|
|
30
|
+
bannerType?: "error" | "success" | "warning" | "info"
|
|
31
|
+
showBannerIcon?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const StyledCardContainer = styled(View)<{
|
|
35
|
+
cardDevice: ProductDisplayCardDevice
|
|
36
|
+
}>(({ theme }) => {
|
|
37
|
+
const { spacing, borderRadius } = theme.tokens.semantics.dimensions
|
|
38
|
+
const { colour } = theme.tokens.semantics
|
|
39
|
+
const spacingMd = parseTokenValue(spacing.md)
|
|
40
|
+
const spacingXl = parseTokenValue(spacing.xl)
|
|
41
|
+
return {
|
|
42
|
+
flexDirection: "row",
|
|
43
|
+
width: "100%",
|
|
44
|
+
height: "100%",
|
|
45
|
+
maxHeight: spacingXl * 6 + spacingMd,
|
|
46
|
+
backgroundColor: colour.background.surface.default,
|
|
47
|
+
borderWidth: 1,
|
|
48
|
+
borderColor: colour.border.default,
|
|
49
|
+
borderRadius: parseTokenValue(borderRadius.md),
|
|
50
|
+
overflow: "hidden",
|
|
51
|
+
shadowColor: "#522a10",
|
|
52
|
+
shadowOffset: { width: 0, height: spacingMd / 4 },
|
|
53
|
+
shadowOpacity: 0.05,
|
|
54
|
+
shadowRadius: spacingXl - 4,
|
|
55
|
+
elevation: 5,
|
|
56
|
+
zIndex: 1
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const StyledImage = styled(View)<{ imageBackgroundColor?: string }>(
|
|
61
|
+
({ theme, imageBackgroundColor }) => ({
|
|
62
|
+
flexShrink: 0,
|
|
63
|
+
backgroundColor:
|
|
64
|
+
imageBackgroundColor ||
|
|
65
|
+
theme.tokens.semantics.colour.background.container.secondary,
|
|
66
|
+
overflow: "hidden",
|
|
67
|
+
aspectRatio: "1 / 1",
|
|
68
|
+
height: "100%",
|
|
69
|
+
position: "relative"
|
|
70
|
+
})
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
const StyledQuantityBadge = styled(View)(({ theme }) => {
|
|
74
|
+
const spacing = theme.tokens.semantics.dimensions.spacing
|
|
75
|
+
const borderRadius = theme.tokens.semantics.dimensions.borderRadius
|
|
76
|
+
const { colour } = theme.tokens.semantics
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
position: "absolute",
|
|
80
|
+
top: parseTokenValue(spacing["2xs"]),
|
|
81
|
+
right: parseTokenValue(spacing["2xs"]),
|
|
82
|
+
backgroundColor: colour.background.container.brand,
|
|
83
|
+
borderRadius: parseTokenValue(borderRadius.xs),
|
|
84
|
+
paddingHorizontal: parseTokenValue(spacing.sm),
|
|
85
|
+
height: parseTokenValue(spacing["2xl"]),
|
|
86
|
+
minWidth: parseTokenValue(spacing["2xl"]),
|
|
87
|
+
alignItems: "center",
|
|
88
|
+
justifyContent: "center",
|
|
89
|
+
zIndex: 2
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const StyledContentArea = styled(View)<{
|
|
94
|
+
contentDevice: ProductDisplayCardDevice
|
|
95
|
+
}>(({ theme }) => {
|
|
96
|
+
const { spacing } = theme.tokens.semantics.dimensions
|
|
97
|
+
return {
|
|
98
|
+
flex: 1,
|
|
99
|
+
paddingHorizontal: parseTokenValue(spacing.md),
|
|
100
|
+
paddingVertical: parseTokenValue(spacing.md),
|
|
101
|
+
gap: parseTokenValue(spacing.xs),
|
|
102
|
+
justifyContent: "space-between"
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const StyledTextContainer = styled(View)(() => ({
|
|
107
|
+
gap: 2
|
|
108
|
+
}))
|
|
109
|
+
|
|
110
|
+
const StyledBannerWrapper = styled(View)(() => ({
|
|
111
|
+
width: "100%",
|
|
112
|
+
marginTop: -48,
|
|
113
|
+
paddingTop: 24,
|
|
114
|
+
paddingBottom: 16
|
|
115
|
+
}))
|
|
116
|
+
|
|
117
|
+
const StyledRootWrapper = styled(View)(() => {
|
|
118
|
+
return {
|
|
119
|
+
flexDirection: "column",
|
|
120
|
+
width: "100%",
|
|
121
|
+
height: "100%"
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const StyledNotification = styled(Notification as any)(({ theme }) => {
|
|
126
|
+
const { spacing } = theme.tokens.semantics.dimensions
|
|
127
|
+
return {
|
|
128
|
+
top: parseTokenValue(spacing.md),
|
|
129
|
+
paddingHorizontal: parseTokenValue(spacing.xl),
|
|
130
|
+
paddingVertical: parseTokenValue(spacing.md)
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Product Display Card component for showing product information with optional quantity picker.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```tsx
|
|
139
|
+
* import { ProductDisplayCard } from "@butternutbox/pawprint-native"
|
|
140
|
+
*
|
|
141
|
+
* <ProductDisplayCard
|
|
142
|
+
* device="mobile"
|
|
143
|
+
* title="Premium Recipe"
|
|
144
|
+
* subtext="+£0.00/pouch"
|
|
145
|
+
* quantity={1}
|
|
146
|
+
* showQuantityPicker
|
|
147
|
+
* />
|
|
148
|
+
* ```
|
|
149
|
+
*
|
|
150
|
+
* @param device - *(optional)* Device variant: "desktop" or "mobile" (default)
|
|
151
|
+
* @param title - Product title
|
|
152
|
+
* @param subtext - *(optional)* Subtitle text or JSX element (e.g. price)
|
|
153
|
+
* @param showSubtext - *(optional)* Show/hide subtext
|
|
154
|
+
* @param quantity - *(optional)* Current quantity value
|
|
155
|
+
* @param onQuantityChange - *(optional)* Callback when quantity changes
|
|
156
|
+
* @param showQuantityPicker - *(optional)* Show/hide quantity picker
|
|
157
|
+
* @param image - *(optional)* Image URL string or React element/JSX content
|
|
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
|
|
160
|
+
* @param banner - *(optional)* Banner content to show below card
|
|
161
|
+
* @param showBanner - *(optional)* Show/hide banner
|
|
162
|
+
* @param bannerType - *(optional)* Banner notification type: "error", "success", "warning", or "info" (default)
|
|
163
|
+
* @param showBannerIcon - *(optional)* Show/hide banner notification icon
|
|
164
|
+
*/
|
|
165
|
+
export const ProductDisplayCard = React.forwardRef<
|
|
166
|
+
View,
|
|
167
|
+
ProductDisplayCardProps
|
|
168
|
+
>(
|
|
169
|
+
(
|
|
170
|
+
{
|
|
171
|
+
device = "mobile",
|
|
172
|
+
title,
|
|
173
|
+
subtext,
|
|
174
|
+
showSubtext = true,
|
|
175
|
+
quantity = 1,
|
|
176
|
+
onQuantityChange,
|
|
177
|
+
showQuantityPicker = false,
|
|
178
|
+
disableQuantityButtons = false,
|
|
179
|
+
hideQuantityButtons = false,
|
|
180
|
+
showIncrementButton,
|
|
181
|
+
showDecrementButton,
|
|
182
|
+
incrementDisabled = false,
|
|
183
|
+
decrementDisabled = false,
|
|
184
|
+
image,
|
|
185
|
+
imageBackgroundColor,
|
|
186
|
+
showImageQuantityBadge = false,
|
|
187
|
+
banner,
|
|
188
|
+
showBanner = false,
|
|
189
|
+
bannerType = "info",
|
|
190
|
+
showBannerIcon = false,
|
|
191
|
+
...rest
|
|
192
|
+
},
|
|
193
|
+
ref
|
|
194
|
+
) => {
|
|
195
|
+
const handleQuantityChange = (newQuantity: number) => {
|
|
196
|
+
if (newQuantity >= 1) {
|
|
197
|
+
onQuantityChange?.(newQuantity)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const cardContent = (
|
|
202
|
+
<StyledCardContainer ref={ref} cardDevice={device} {...rest}>
|
|
203
|
+
{image && (
|
|
204
|
+
<StyledImage imageBackgroundColor={imageBackgroundColor}>
|
|
205
|
+
{typeof image === "string" ? (
|
|
206
|
+
<Image
|
|
207
|
+
source={{ uri: image }}
|
|
208
|
+
style={{ width: "100%", height: "100%" }}
|
|
209
|
+
/>
|
|
210
|
+
) : (
|
|
211
|
+
image
|
|
212
|
+
)}
|
|
213
|
+
{showImageQuantityBadge && quantity && (
|
|
214
|
+
<StyledQuantityBadge>
|
|
215
|
+
<Typography variant="body" size="sm" weight="bold">
|
|
216
|
+
{quantity}
|
|
217
|
+
</Typography>
|
|
218
|
+
</StyledQuantityBadge>
|
|
219
|
+
)}
|
|
220
|
+
</StyledImage>
|
|
221
|
+
)}
|
|
222
|
+
|
|
223
|
+
<StyledContentArea contentDevice={device}>
|
|
224
|
+
<StyledTextContainer>
|
|
225
|
+
<Typography variant="heading" size="xs" color="primary">
|
|
226
|
+
{title}
|
|
227
|
+
</Typography>
|
|
228
|
+
|
|
229
|
+
{showSubtext && subtext && (
|
|
230
|
+
<Typography variant="body" size="sm" color="secondary">
|
|
231
|
+
{subtext}
|
|
232
|
+
</Typography>
|
|
233
|
+
)}
|
|
234
|
+
</StyledTextContainer>
|
|
235
|
+
|
|
236
|
+
{showQuantityPicker && (
|
|
237
|
+
<View style={{ marginLeft: "auto" }}>
|
|
238
|
+
<NumberField
|
|
239
|
+
size="sm"
|
|
240
|
+
value={String(quantity)}
|
|
241
|
+
onChangeText={(text) => {
|
|
242
|
+
const value = parseInt(text, 10)
|
|
243
|
+
if (!isNaN(value)) {
|
|
244
|
+
handleQuantityChange(value)
|
|
245
|
+
}
|
|
246
|
+
}}
|
|
247
|
+
onIncrement={() => handleQuantityChange(quantity + 1)}
|
|
248
|
+
onDecrement={() => handleQuantityChange(quantity - 1)}
|
|
249
|
+
min={1}
|
|
250
|
+
accessible
|
|
251
|
+
accessibilityLabel={`Quantity: ${quantity}`}
|
|
252
|
+
showIncrementButton={
|
|
253
|
+
showIncrementButton !== undefined
|
|
254
|
+
? showIncrementButton
|
|
255
|
+
: !hideQuantityButtons
|
|
256
|
+
}
|
|
257
|
+
showDecrementButton={
|
|
258
|
+
showDecrementButton !== undefined
|
|
259
|
+
? showDecrementButton
|
|
260
|
+
: !hideQuantityButtons
|
|
261
|
+
}
|
|
262
|
+
incrementDisabled={incrementDisabled || disableQuantityButtons}
|
|
263
|
+
decrementDisabled={decrementDisabled || disableQuantityButtons}
|
|
264
|
+
/>
|
|
265
|
+
</View>
|
|
266
|
+
)}
|
|
267
|
+
</StyledContentArea>
|
|
268
|
+
</StyledCardContainer>
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
if (showBanner && banner) {
|
|
272
|
+
return (
|
|
273
|
+
<StyledRootWrapper>
|
|
274
|
+
{cardContent}
|
|
275
|
+
<StyledBannerWrapper>
|
|
276
|
+
<StyledNotification
|
|
277
|
+
variant="inline"
|
|
278
|
+
type={bannerType}
|
|
279
|
+
showIcon={showBannerIcon}
|
|
280
|
+
>
|
|
281
|
+
{banner}
|
|
282
|
+
</StyledNotification>
|
|
283
|
+
</StyledBannerWrapper>
|
|
284
|
+
</StyledRootWrapper>
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return cardContent
|
|
289
|
+
}
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
ProductDisplayCard.displayName = "ProductDisplayCard"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { View, ViewProps } from "react-native"
|
|
3
|
+
import styled from "@emotion/native"
|
|
4
|
+
import { Typography } from "../../atoms/Typography"
|
|
5
|
+
|
|
6
|
+
const parseTokenValue = (value: string | number): number => {
|
|
7
|
+
if (typeof value === "number") return value
|
|
8
|
+
return parseFloat(value)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type BadgeOwnProps = {
|
|
12
|
+
icon?: React.ReactNode
|
|
13
|
+
text: React.ReactNode
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type BadgeProps = BadgeOwnProps & Omit<ViewProps, keyof BadgeOwnProps>
|
|
17
|
+
|
|
18
|
+
const BadgeContainer = styled(View)(({ theme }) => {
|
|
19
|
+
const spacing = theme.tokens.semantics.dimensions.spacing
|
|
20
|
+
const { sizing, borderRadius } = theme.tokens.semantics.dimensions
|
|
21
|
+
const { colour } = theme.tokens.semantics
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
display: "flex",
|
|
25
|
+
flexDirection: "row",
|
|
26
|
+
gap: parseTokenValue(spacing["2xs"]),
|
|
27
|
+
alignItems: "center",
|
|
28
|
+
justifyContent: "center",
|
|
29
|
+
height: parseTokenValue(sizing["2xl"]),
|
|
30
|
+
paddingRight: parseTokenValue(spacing.sm),
|
|
31
|
+
backgroundColor: colour.background.container.alt,
|
|
32
|
+
borderTopRightRadius: parseTokenValue(borderRadius.xs),
|
|
33
|
+
borderBottomRightRadius: parseTokenValue(borderRadius.xs)
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const BadgeIcon = styled(View)(({ theme }) => {
|
|
38
|
+
const spacing = theme.tokens.semantics.dimensions.spacing
|
|
39
|
+
const sizing = theme.tokens.semantics.dimensions.sizing
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
display: "flex",
|
|
43
|
+
alignItems: "center",
|
|
44
|
+
justifyContent: "center",
|
|
45
|
+
width: parseTokenValue(sizing.md),
|
|
46
|
+
height: parseTokenValue(sizing.md),
|
|
47
|
+
marginLeft: parseTokenValue(spacing.xs),
|
|
48
|
+
flexShrink: 0
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
export const Badge = React.forwardRef<View, BadgeProps>(
|
|
53
|
+
({ icon, text, ...rest }, ref) => {
|
|
54
|
+
return (
|
|
55
|
+
<BadgeContainer ref={ref} {...rest}>
|
|
56
|
+
{icon && <BadgeIcon>{icon}</BadgeIcon>}
|
|
57
|
+
<Typography variant="body" size="sm" weight="bold">
|
|
58
|
+
{text}
|
|
59
|
+
</Typography>
|
|
60
|
+
</BadgeContainer>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
Badge.displayName = "ProductListingCard.Badge"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { View, ViewProps, useWindowDimensions } from "react-native"
|
|
3
|
+
import styled from "@emotion/native"
|
|
4
|
+
import { DEFAULT_THEME_OPTIONS } from "@butternutbox/pawprint-tokens"
|
|
5
|
+
|
|
6
|
+
type ProductListingCardGridOwnProps = {
|
|
7
|
+
children: React.ReactNode
|
|
8
|
+
xs?: number
|
|
9
|
+
md?: number
|
|
10
|
+
lg?: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type ProductListingCardGridProps = ProductListingCardGridOwnProps &
|
|
14
|
+
Omit<ViewProps, keyof ProductListingCardGridOwnProps>
|
|
15
|
+
|
|
16
|
+
const StyledGrid = styled(View)(({ theme }) => {
|
|
17
|
+
const spacing = theme.tokens.semantics.dimensions.spacing
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
display: "grid",
|
|
21
|
+
"grid-column-gap": spacing.sm,
|
|
22
|
+
"grid-row-gap": spacing.xl
|
|
23
|
+
} as any
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export const Grid = React.forwardRef<View, ProductListingCardGridProps>(
|
|
27
|
+
({ children, xs = 1, md = 2, lg = 3, ...rest }, ref) => {
|
|
28
|
+
const { width } = useWindowDimensions()
|
|
29
|
+
|
|
30
|
+
const breakpoints =
|
|
31
|
+
DEFAULT_THEME_OPTIONS.tokens.semantics.dimensions.breakpoints.spacing
|
|
32
|
+
const SM_BREAKPOINT = parseFloat(String(breakpoints.sm))
|
|
33
|
+
const MD_BREAKPOINT = parseFloat(String(breakpoints.md))
|
|
34
|
+
|
|
35
|
+
const getItemsPerRow = () => {
|
|
36
|
+
if (width >= MD_BREAKPOINT) return lg
|
|
37
|
+
if (width >= SM_BREAKPOINT) return md
|
|
38
|
+
return xs
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const itemsPerRow = getItemsPerRow()
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<StyledGrid
|
|
45
|
+
ref={ref}
|
|
46
|
+
style={
|
|
47
|
+
{
|
|
48
|
+
gridTemplateColumns: `repeat(${itemsPerRow}, 1fr)`
|
|
49
|
+
} as any
|
|
50
|
+
}
|
|
51
|
+
{...rest}
|
|
52
|
+
>
|
|
53
|
+
{children}
|
|
54
|
+
</StyledGrid>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
Grid.displayName = "ProductListingCard.Grid"
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { View } from "react-native"
|
|
3
|
+
import { ProductListingCard } from "./ProductListingCard"
|
|
4
|
+
import { Badge } from "./Badge"
|
|
5
|
+
import { ThumbsUpFilledPrimary } from "@butternutbox/pawprint-icons/marketing"
|
|
6
|
+
|
|
7
|
+
const defaultBadge = (
|
|
8
|
+
<Badge icon={<ThumbsUpFilledPrimary />} text="New & improved" />
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
const placeholderImage = {
|
|
12
|
+
uri: "https://placehold.co/232x232"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default {
|
|
16
|
+
title: "Molecules/ProductListingCard",
|
|
17
|
+
component: ProductListingCard,
|
|
18
|
+
argTypes: {
|
|
19
|
+
image: {
|
|
20
|
+
description: "Product image source"
|
|
21
|
+
},
|
|
22
|
+
imageAlt: {
|
|
23
|
+
control: "text",
|
|
24
|
+
description: "Alt text for the product image"
|
|
25
|
+
},
|
|
26
|
+
badge: {
|
|
27
|
+
description: "Badge content to display in the top-left corner"
|
|
28
|
+
},
|
|
29
|
+
category: {
|
|
30
|
+
description: "Product category Typography element(s)"
|
|
31
|
+
},
|
|
32
|
+
title: {
|
|
33
|
+
description: "Product name/title Typography element(s)"
|
|
34
|
+
},
|
|
35
|
+
size: {
|
|
36
|
+
description: "Product size or variant Typography element(s)"
|
|
37
|
+
},
|
|
38
|
+
price: {
|
|
39
|
+
description: "Price content Typography element(s)"
|
|
40
|
+
},
|
|
41
|
+
showBadge: {
|
|
42
|
+
control: "boolean",
|
|
43
|
+
description: "Toggle badge visibility"
|
|
44
|
+
},
|
|
45
|
+
showCategory: {
|
|
46
|
+
control: "boolean",
|
|
47
|
+
description: "Toggle category visibility"
|
|
48
|
+
},
|
|
49
|
+
showTitle: {
|
|
50
|
+
control: "boolean",
|
|
51
|
+
description: "Toggle product title visibility"
|
|
52
|
+
},
|
|
53
|
+
showSize: {
|
|
54
|
+
control: "boolean",
|
|
55
|
+
description: "Toggle product size visibility"
|
|
56
|
+
},
|
|
57
|
+
showPrice: {
|
|
58
|
+
control: "boolean",
|
|
59
|
+
description: "Toggle price visibility"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const Default = {
|
|
65
|
+
render: () => (
|
|
66
|
+
<View style={{ width: 232, alignSelf: "flex-start" }}>
|
|
67
|
+
<ProductListingCard
|
|
68
|
+
image={placeholderImage}
|
|
69
|
+
imageAlt="Fish Oil Product"
|
|
70
|
+
badge={defaultBadge}
|
|
71
|
+
category="Daily supplements"
|
|
72
|
+
title="Fish Oil"
|
|
73
|
+
size="200ml"
|
|
74
|
+
price="From £7.49"
|
|
75
|
+
showBadge={true}
|
|
76
|
+
showCategory={true}
|
|
77
|
+
showTitle={true}
|
|
78
|
+
showSize={true}
|
|
79
|
+
showPrice={true}
|
|
80
|
+
/>
|
|
81
|
+
</View>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const WithoutBadge = {
|
|
86
|
+
render: () => (
|
|
87
|
+
<View style={{ width: 232, alignSelf: "flex-start" }}>
|
|
88
|
+
<ProductListingCard
|
|
89
|
+
image={placeholderImage}
|
|
90
|
+
imageAlt="Fish Oil Product"
|
|
91
|
+
category="Daily supplements"
|
|
92
|
+
title="Fish Oil"
|
|
93
|
+
size="200ml"
|
|
94
|
+
price="From £7.49"
|
|
95
|
+
showBadge={false}
|
|
96
|
+
showCategory={true}
|
|
97
|
+
showTitle={true}
|
|
98
|
+
showSize={true}
|
|
99
|
+
showPrice={true}
|
|
100
|
+
/>
|
|
101
|
+
</View>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const MinimalInfo = {
|
|
106
|
+
render: () => (
|
|
107
|
+
<View style={{ width: 232, alignSelf: "flex-start" }}>
|
|
108
|
+
<ProductListingCard
|
|
109
|
+
image={{ uri: "https://placeholder.co/232x232" }}
|
|
110
|
+
imageAlt="Product"
|
|
111
|
+
title="Product Name"
|
|
112
|
+
price="£9.99"
|
|
113
|
+
showBadge={false}
|
|
114
|
+
showCategory={false}
|
|
115
|
+
showSize={false}
|
|
116
|
+
/>
|
|
117
|
+
</View>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export const FullyCustomized = {
|
|
122
|
+
render: () => (
|
|
123
|
+
<View style={{ width: 232, alignSelf: "flex-start" }}>
|
|
124
|
+
<ProductListingCard
|
|
125
|
+
image={{ uri: "https://placehold.co/232x232" }}
|
|
126
|
+
imageAlt="Premium Item"
|
|
127
|
+
badge={<Badge icon="⭐" text="Premium" />}
|
|
128
|
+
category="Premium Collection"
|
|
129
|
+
title="Deluxe Supplement Pack"
|
|
130
|
+
size="500ml x 3 bottles"
|
|
131
|
+
price="From £24.99"
|
|
132
|
+
showBadge={true}
|
|
133
|
+
showCategory={true}
|
|
134
|
+
showTitle={true}
|
|
135
|
+
showSize={true}
|
|
136
|
+
showPrice={true}
|
|
137
|
+
/>
|
|
138
|
+
</View>
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const Grid = {
|
|
143
|
+
render: () => (
|
|
144
|
+
<View style={{ width: "100%", padding: 16 }}>
|
|
145
|
+
<ProductListingCard.Grid xs={1} md={2} lg={3}>
|
|
146
|
+
{Array.from({ length: 6 }).map((_, i) => (
|
|
147
|
+
<ProductListingCard
|
|
148
|
+
key={i}
|
|
149
|
+
image={placeholderImage}
|
|
150
|
+
imageAlt="Fish Oil Product"
|
|
151
|
+
badge={defaultBadge}
|
|
152
|
+
category="Daily supplements"
|
|
153
|
+
title="Fish Oil"
|
|
154
|
+
size="200ml"
|
|
155
|
+
price="From £7.49"
|
|
156
|
+
showBadge={true}
|
|
157
|
+
showCategory={true}
|
|
158
|
+
showTitle={true}
|
|
159
|
+
showSize={true}
|
|
160
|
+
showPrice={true}
|
|
161
|
+
/>
|
|
162
|
+
))}
|
|
163
|
+
</ProductListingCard.Grid>
|
|
164
|
+
</View>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const Playground = {
|
|
169
|
+
render: () => (
|
|
170
|
+
<View style={{ width: 232, alignSelf: "flex-start" }}>
|
|
171
|
+
<ProductListingCard
|
|
172
|
+
image={placeholderImage}
|
|
173
|
+
imageAlt="Fish Oil Product"
|
|
174
|
+
badge={defaultBadge}
|
|
175
|
+
category="Daily supplements"
|
|
176
|
+
title="Fish Oil"
|
|
177
|
+
size="200ml"
|
|
178
|
+
price="From £7.49"
|
|
179
|
+
showBadge={true}
|
|
180
|
+
showCategory={true}
|
|
181
|
+
showTitle={true}
|
|
182
|
+
showSize={true}
|
|
183
|
+
showPrice={true}
|
|
184
|
+
/>
|
|
185
|
+
</View>
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export const WithWasPrice = {
|
|
190
|
+
render: () => (
|
|
191
|
+
<View style={{ width: 232, alignSelf: "flex-start" }}>
|
|
192
|
+
<ProductListingCard
|
|
193
|
+
image={placeholderImage}
|
|
194
|
+
imageAlt="Fish Oil Product"
|
|
195
|
+
badge={defaultBadge}
|
|
196
|
+
category="Daily supplements"
|
|
197
|
+
title="Fish Oil"
|
|
198
|
+
size="200ml"
|
|
199
|
+
price="From £7.49"
|
|
200
|
+
wasPrice="£12.50"
|
|
201
|
+
showBadge={true}
|
|
202
|
+
showCategory={true}
|
|
203
|
+
showTitle={true}
|
|
204
|
+
showSize={true}
|
|
205
|
+
showPrice={true}
|
|
206
|
+
/>
|
|
207
|
+
</View>
|
|
208
|
+
)
|
|
209
|
+
}
|