@shopify/shop-minis-react 0.0.32 → 0.0.34
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/dist/_virtual/index2.js +4 -4
- package/dist/_virtual/index3.js +4 -4
- package/dist/_virtual/index4.js +2 -2
- package/dist/_virtual/index5.js +3 -2
- package/dist/_virtual/index5.js.map +1 -1
- package/dist/_virtual/index6.js +2 -2
- package/dist/_virtual/index7.js +2 -3
- package/dist/_virtual/index7.js.map +1 -1
- package/dist/_virtual/index8.js +2 -2
- package/dist/_virtual/index9.js +2 -2
- package/dist/components/atoms/image.js +52 -0
- package/dist/components/atoms/image.js.map +1 -0
- package/dist/components/commerce/merchant-card.js +188 -245
- package/dist/components/commerce/merchant-card.js.map +1 -1
- package/dist/components/commerce/product-card.js +11 -11
- package/dist/components/commerce/product-card.js.map +1 -1
- package/dist/components/content/image-content-wrapper.js +29 -22
- package/dist/components/content/image-content-wrapper.js.map +1 -1
- package/dist/hooks/content/useCreateImageContent.js +16 -22
- package/dist/hooks/content/useCreateImageContent.js.map +1 -1
- package/dist/hooks/storage/useImageUpload.js +36 -37
- package/dist/hooks/storage/useImageUpload.js.map +1 -1
- package/dist/index.js +252 -246
- package/dist/shop-minis-platform/src/types/content.js.map +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/@radix-ui_react-use-is-hydrated@0.1.0_@types_react@19.1.6_react@19.1.0/node_modules/@radix-ui/react-use-is-hydrated/dist/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/@videojs_xhr@2.7.0/node_modules/@videojs/xhr/lib/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/@xmldom_xmldom@0.8.10/node_modules/@xmldom/xmldom/lib/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/color-string@1.9.1/node_modules/color-string/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/mpd-parser@1.3.1/node_modules/mpd-parser/dist/mpd-parser.es.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/querystringify@2.2.0/node_modules/querystringify/index.js +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/video.js@8.23.3/node_modules/video.js/dist/video.es.js +1 -1
- package/dist/utils/colors.js +1 -1
- package/dist/utils/image.js +45 -9
- package/dist/utils/image.js.map +1 -1
- package/package.json +2 -2
- package/src/components/atoms/{thumbhash-image.tsx → image.tsx} +14 -14
- package/src/components/commerce/merchant-card.tsx +224 -225
- package/src/components/commerce/product-card.tsx +2 -2
- package/src/components/content/image-content-wrapper.tsx +9 -2
- package/src/components/index.ts +1 -1
- package/src/hooks/content/useCreateImageContent.ts +1 -7
- package/src/hooks/storage/useImageUpload.ts +22 -20
- package/src/stories/MerchantCard.stories.tsx +0 -3
- package/src/utils/image.ts +72 -0
- package/src/utils/index.ts +1 -1
- package/dist/components/atoms/thumbhash-image.js +0 -54
- package/dist/components/atoms/thumbhash-image.js.map +0 -1
- package/dist/utils/imageToDataUri.js +0 -10
- package/dist/utils/imageToDataUri.js.map +0 -1
- package/src/utils/imageToDataUri.ts +0 -8
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import * as React from 'react'
|
|
2
|
+
import {createContext, useCallback, useContext, useMemo} from 'react'
|
|
2
3
|
|
|
3
4
|
import {type Shop} from '@shopify/shop-minis-platform'
|
|
4
|
-
import {cva, type VariantProps} from 'class-variance-authority'
|
|
5
5
|
import {Star} from 'lucide-react'
|
|
6
|
-
import {Slot as SlotPrimitive} from 'radix-ui'
|
|
7
6
|
|
|
8
7
|
import {useShopNavigation} from '../../hooks/navigation/useShopNavigation'
|
|
9
8
|
import {cn} from '../../lib/utils'
|
|
@@ -15,52 +14,62 @@ import {
|
|
|
15
14
|
normalizeRating,
|
|
16
15
|
} from '../../utils'
|
|
17
16
|
import {isDarkColor} from '../../utils/colors'
|
|
18
|
-
import {
|
|
17
|
+
import {Image} from '../atoms/image'
|
|
19
18
|
import {Touchable} from '../atoms/touchable'
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
interface MerchantCardContextValue {
|
|
21
|
+
// Core data
|
|
22
|
+
shop: Shop
|
|
23
|
+
|
|
24
|
+
// Derived data
|
|
25
|
+
cardTheme: ExtractedBrandTheme
|
|
26
|
+
|
|
27
|
+
// UI configuration
|
|
28
|
+
touchable: boolean
|
|
29
|
+
featuredImagesLimit: number
|
|
30
|
+
|
|
31
|
+
// Actions
|
|
32
|
+
onClick: () => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const MerchantCardContext = createContext<MerchantCardContextValue | undefined>(
|
|
36
|
+
undefined
|
|
34
37
|
)
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
function useMerchantCardContext() {
|
|
40
|
+
const context = useContext(MerchantCardContext)
|
|
41
|
+
if (!context) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
'useMerchantCardContext must be used within a MerchantCardProvider'
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
return context
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
function
|
|
49
|
+
function MerchantCardContainer({
|
|
45
50
|
className,
|
|
46
|
-
touchable = true,
|
|
47
|
-
asChild = false,
|
|
48
|
-
onPress,
|
|
49
51
|
...props
|
|
50
|
-
}:
|
|
51
|
-
const
|
|
52
|
+
}: React.ComponentProps<'div'>) {
|
|
53
|
+
const {touchable, cardTheme, onClick} = useMerchantCardContext()
|
|
52
54
|
|
|
53
55
|
const content = (
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
+
<div
|
|
57
|
+
style={{
|
|
58
|
+
backgroundColor: cardTheme.backgroundColor,
|
|
59
|
+
}}
|
|
60
|
+
className={cn(
|
|
61
|
+
'relative w-full overflow-hidden rounded-xl bg-white flex flex-col border border-gray-200 aspect-square',
|
|
62
|
+
|
|
63
|
+
className
|
|
64
|
+
)}
|
|
56
65
|
{...props}
|
|
57
66
|
/>
|
|
58
67
|
)
|
|
59
68
|
|
|
60
|
-
if (touchable &&
|
|
69
|
+
if (touchable && onClick) {
|
|
61
70
|
return (
|
|
62
71
|
<Touchable
|
|
63
|
-
onClick={
|
|
72
|
+
onClick={onClick}
|
|
64
73
|
whileTap={{opacity: 0.7}}
|
|
65
74
|
transition={{
|
|
66
75
|
opacity: {type: 'tween', duration: 0.08, ease: 'easeInOut'},
|
|
@@ -74,19 +83,6 @@ function MerchantCardRoot({
|
|
|
74
83
|
return content
|
|
75
84
|
}
|
|
76
85
|
|
|
77
|
-
function MerchantCardImageContainer({
|
|
78
|
-
className,
|
|
79
|
-
...props
|
|
80
|
-
}: React.ComponentProps<'div'>) {
|
|
81
|
-
return (
|
|
82
|
-
<div
|
|
83
|
-
data-slot="merchant-card-image-container"
|
|
84
|
-
className={cn('relative overflow-hidden w-full flex-grow', className)}
|
|
85
|
-
{...props}
|
|
86
|
-
/>
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
86
|
function MerchantCardImage({
|
|
91
87
|
className,
|
|
92
88
|
src,
|
|
@@ -104,12 +100,12 @@ function MerchantCardImage({
|
|
|
104
100
|
|
|
105
101
|
if (thumbhash) {
|
|
106
102
|
return (
|
|
107
|
-
<
|
|
103
|
+
<Image
|
|
108
104
|
data-slot="merchant-card-image"
|
|
109
105
|
src={src}
|
|
110
106
|
alt={alt}
|
|
111
107
|
thumbhash={thumbhash}
|
|
112
|
-
className={cn(
|
|
108
|
+
className={cn(className)}
|
|
113
109
|
{...props}
|
|
114
110
|
/>
|
|
115
111
|
)
|
|
@@ -120,23 +116,28 @@ function MerchantCardImage({
|
|
|
120
116
|
data-slot="merchant-card-image"
|
|
121
117
|
src={src}
|
|
122
118
|
alt={alt}
|
|
123
|
-
className={cn('
|
|
119
|
+
className={cn('size-full object-cover', className)}
|
|
124
120
|
{...props}
|
|
125
121
|
/>
|
|
126
122
|
)
|
|
127
123
|
}
|
|
128
124
|
|
|
129
|
-
function MerchantCardLogo({
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
125
|
+
function MerchantCardLogo({className, ...props}: React.ComponentProps<'div'>) {
|
|
126
|
+
const {shop} = useMerchantCardContext()
|
|
127
|
+
const {name, visualTheme} = shop
|
|
128
|
+
|
|
129
|
+
const logoAverageColor = visualTheme?.brandSettings?.colors?.logoAverage
|
|
130
|
+
const logoDominantColor = visualTheme?.brandSettings?.colors?.logoDominant
|
|
131
|
+
const logoColor = logoAverageColor || logoDominantColor
|
|
132
|
+
|
|
133
|
+
const logoBackgroundClassName = useMemo(
|
|
134
|
+
() => (logoColor && isDarkColor(logoColor) ? 'bg-white' : 'bg-gray-800'),
|
|
135
|
+
[logoColor]
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
const logoUrl = visualTheme?.logoImage?.url
|
|
139
|
+
const altText = `${name} logo`
|
|
140
|
+
|
|
140
141
|
return (
|
|
141
142
|
<div
|
|
142
143
|
data-slot="merchant-card-logo"
|
|
@@ -144,16 +145,21 @@ function MerchantCardLogo({
|
|
|
144
145
|
'absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-10',
|
|
145
146
|
'w-16 h-16 rounded-xl bg-white border-2 border-white shadow-sm',
|
|
146
147
|
'flex items-center justify-center overflow-hidden',
|
|
148
|
+
logoBackgroundClassName,
|
|
147
149
|
className
|
|
148
150
|
)}
|
|
149
151
|
{...props}
|
|
150
152
|
>
|
|
151
|
-
{
|
|
152
|
-
<img
|
|
153
|
+
{logoUrl ? (
|
|
154
|
+
<img
|
|
155
|
+
src={logoUrl}
|
|
156
|
+
alt={altText}
|
|
157
|
+
className="w-full h-full object-cover"
|
|
158
|
+
/>
|
|
153
159
|
) : (
|
|
154
160
|
<div className="w-full h-full bg-gray-200 flex items-center justify-center">
|
|
155
161
|
<span className="text-gray-600 font-semibold text-lg">
|
|
156
|
-
{
|
|
162
|
+
{name?.slice(0, 1)}
|
|
157
163
|
</span>
|
|
158
164
|
</div>
|
|
159
165
|
)}
|
|
@@ -162,11 +168,23 @@ function MerchantCardLogo({
|
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
function MerchantCardInfo({className, ...props}: React.ComponentProps<'div'>) {
|
|
171
|
+
const {cardTheme} = useMerchantCardContext()
|
|
172
|
+
|
|
173
|
+
const isDarkTheme = useMemo(() => {
|
|
174
|
+
return (
|
|
175
|
+
cardTheme.backgroundColor !== 'white' &&
|
|
176
|
+
isDarkColor(cardTheme.backgroundColor)
|
|
177
|
+
)
|
|
178
|
+
}, [cardTheme.backgroundColor])
|
|
179
|
+
|
|
180
|
+
const textColor = isDarkTheme ? 'text-primary-foreground' : 'text-foreground'
|
|
181
|
+
|
|
165
182
|
return (
|
|
166
183
|
<div
|
|
167
184
|
data-slot="merchant-card-info"
|
|
168
185
|
className={cn(
|
|
169
|
-
'p-3
|
|
186
|
+
'p-3 flex-shrink-0 flex flex-col min-w-0',
|
|
187
|
+
textColor,
|
|
170
188
|
className
|
|
171
189
|
)}
|
|
172
190
|
{...props}
|
|
@@ -179,31 +197,36 @@ function MerchantCardName({
|
|
|
179
197
|
children,
|
|
180
198
|
...props
|
|
181
199
|
}: React.ComponentProps<'h3'>) {
|
|
200
|
+
const {shop} = useMerchantCardContext()
|
|
201
|
+
const {name} = shop
|
|
202
|
+
const nameContent = children ?? name
|
|
203
|
+
|
|
182
204
|
return (
|
|
183
205
|
<h3
|
|
184
206
|
data-slot="merchant-card-name"
|
|
185
|
-
className={cn(
|
|
186
|
-
'text-sm font-medium text-grayscale-d100',
|
|
187
|
-
'truncate overflow-hidden whitespace-nowrap text-ellipsis',
|
|
188
|
-
className
|
|
189
|
-
)}
|
|
207
|
+
className={cn('text-sm font-medium truncate', className)}
|
|
190
208
|
{...props}
|
|
191
209
|
>
|
|
192
|
-
{
|
|
210
|
+
{nameContent}
|
|
193
211
|
</h3>
|
|
194
212
|
)
|
|
195
213
|
}
|
|
196
214
|
|
|
197
215
|
function MerchantCardRating({
|
|
198
216
|
className,
|
|
199
|
-
|
|
200
|
-
reviewCount,
|
|
217
|
+
|
|
201
218
|
...props
|
|
202
219
|
}: React.ComponentProps<'div'> & {
|
|
203
220
|
rating?: number | null
|
|
204
221
|
reviewCount?: number
|
|
205
222
|
}) {
|
|
206
|
-
|
|
223
|
+
const {shop} = useMerchantCardContext()
|
|
224
|
+
|
|
225
|
+
const {
|
|
226
|
+
reviewAnalytics: {averageRating, reviewCount},
|
|
227
|
+
} = shop
|
|
228
|
+
|
|
229
|
+
if (!averageRating || !reviewCount) return null
|
|
207
230
|
|
|
208
231
|
return (
|
|
209
232
|
<div
|
|
@@ -213,21 +236,61 @@ function MerchantCardRating({
|
|
|
213
236
|
>
|
|
214
237
|
<Star className="h-3 w-3 fill-current" />
|
|
215
238
|
<span className="text-xs">
|
|
216
|
-
{normalizeRating(
|
|
239
|
+
{normalizeRating(averageRating)} ({formatReviewCount(reviewCount)})
|
|
217
240
|
</span>
|
|
218
241
|
</div>
|
|
219
242
|
)
|
|
220
243
|
}
|
|
221
244
|
|
|
222
|
-
function
|
|
223
|
-
shop,
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
245
|
+
function MerchantCardDefaultHeader({withLogo = false}: {withLogo?: boolean}) {
|
|
246
|
+
const {shop, cardTheme, featuredImagesLimit} = useMerchantCardContext()
|
|
247
|
+
const {visualTheme} = shop
|
|
248
|
+
|
|
249
|
+
const featuredImages = useMemo(
|
|
250
|
+
() => getFeaturedImages(visualTheme, featuredImagesLimit),
|
|
251
|
+
[visualTheme, featuredImagesLimit]
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
const numberOfFeaturedImages = featuredImages?.length ?? 0
|
|
255
|
+
|
|
256
|
+
const displayDefaultCover = () => {
|
|
257
|
+
if (numberOfFeaturedImages > 0) {
|
|
258
|
+
const heightClass = numberOfFeaturedImages === 2 ? 'h-full' : 'h-1/2'
|
|
259
|
+
return featuredImages?.map((image, index) => (
|
|
260
|
+
<div className={`z-0 w-1/2 ${heightClass}`} key={image.url || index}>
|
|
261
|
+
<MerchantCardImage
|
|
262
|
+
src={image.url}
|
|
263
|
+
alt={image.altText ?? undefined}
|
|
264
|
+
thumbhash={image.thumbhash ?? undefined}
|
|
265
|
+
className="aspect-square"
|
|
266
|
+
/>
|
|
267
|
+
</div>
|
|
268
|
+
))
|
|
269
|
+
} else if (cardTheme.type === 'coverImage') {
|
|
270
|
+
return (
|
|
271
|
+
<MerchantCardImage
|
|
272
|
+
src={cardTheme.coverImageUrl}
|
|
273
|
+
thumbhash={cardTheme.coverImageThumbhash}
|
|
274
|
+
/>
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return (
|
|
280
|
+
<div className="w-full h-full bg-muted relative flex flex-wrap overflow-hidden">
|
|
281
|
+
{withLogo && (
|
|
282
|
+
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-10">
|
|
283
|
+
<MerchantCardLogo />
|
|
284
|
+
</div>
|
|
285
|
+
)}
|
|
286
|
+
|
|
287
|
+
{displayDefaultCover()}
|
|
288
|
+
</div>
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function MerchantCardBrandedHeader({withLogo = false}: {withLogo?: boolean}) {
|
|
293
|
+
const {shop, cardTheme} = useMerchantCardContext()
|
|
231
294
|
const wordmarkImage = shop.visualTheme?.brandSettings?.headerTheme?.wordmark
|
|
232
295
|
|
|
233
296
|
return (
|
|
@@ -238,7 +301,6 @@ function MerchantCardBrandedHeader({
|
|
|
238
301
|
src={cardTheme.coverImageUrl}
|
|
239
302
|
alt={shop.name}
|
|
240
303
|
thumbhash={cardTheme.coverImageThumbhash ?? undefined}
|
|
241
|
-
className="size-full"
|
|
242
304
|
/>
|
|
243
305
|
|
|
244
306
|
<div className="absolute inset-0 z-[1] bg-black/20" />
|
|
@@ -252,175 +314,112 @@ function MerchantCardBrandedHeader({
|
|
|
252
314
|
</>
|
|
253
315
|
)}
|
|
254
316
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
/>
|
|
270
|
-
)}
|
|
271
|
-
</div>
|
|
317
|
+
{withLogo && (
|
|
318
|
+
<div className="absolute inset-0 z-[1] flex items-center justify-center">
|
|
319
|
+
{wordmarkImage ? (
|
|
320
|
+
<img
|
|
321
|
+
src={wordmarkImage.url}
|
|
322
|
+
alt={wordmarkImage.altText || shop.name}
|
|
323
|
+
className="max-h-16 min-h-10 max-w-28 object-contain"
|
|
324
|
+
data-testid="store-data-wordmark"
|
|
325
|
+
/>
|
|
326
|
+
) : (
|
|
327
|
+
<MerchantCardLogo />
|
|
328
|
+
)}
|
|
329
|
+
</div>
|
|
330
|
+
)}
|
|
272
331
|
</div>
|
|
273
332
|
)
|
|
274
333
|
}
|
|
334
|
+
|
|
335
|
+
interface MerchantCardHeaderProps {
|
|
336
|
+
isDefault?: boolean
|
|
337
|
+
withLogo?: boolean
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function MerchantCardHeader({
|
|
341
|
+
isDefault,
|
|
342
|
+
withLogo,
|
|
343
|
+
className,
|
|
344
|
+
...props
|
|
345
|
+
}: React.ComponentProps<'div'> & MerchantCardHeaderProps) {
|
|
346
|
+
const {cardTheme} = useMerchantCardContext()
|
|
347
|
+
|
|
348
|
+
const isBranded =
|
|
349
|
+
cardTheme.type === 'coverImage' || cardTheme.type === 'brandColor'
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<div
|
|
353
|
+
className={cn('relative overflow-hidden flex-1 flex-wrap', className)}
|
|
354
|
+
{...props}
|
|
355
|
+
>
|
|
356
|
+
{isBranded && !isDefault ? (
|
|
357
|
+
<MerchantCardBrandedHeader withLogo={withLogo} />
|
|
358
|
+
) : (
|
|
359
|
+
<MerchantCardDefaultHeader withLogo={withLogo} />
|
|
360
|
+
)}
|
|
361
|
+
</div>
|
|
362
|
+
)
|
|
363
|
+
}
|
|
364
|
+
|
|
275
365
|
export interface MerchantCardProps {
|
|
276
366
|
shop: Shop
|
|
277
367
|
touchable?: boolean
|
|
278
|
-
fixedHeight?: boolean
|
|
279
368
|
featuredImagesLimit?: number
|
|
369
|
+
children?: React.ReactNode
|
|
280
370
|
}
|
|
281
371
|
|
|
282
372
|
function MerchantCard({
|
|
283
373
|
shop,
|
|
284
374
|
touchable = true,
|
|
285
|
-
fixedHeight = false,
|
|
286
375
|
featuredImagesLimit = 4,
|
|
376
|
+
children,
|
|
287
377
|
}: MerchantCardProps) {
|
|
288
378
|
const {navigateToShop} = useShopNavigation()
|
|
289
379
|
|
|
290
|
-
const {
|
|
291
|
-
id,
|
|
292
|
-
name,
|
|
293
|
-
reviewAnalytics: {averageRating, reviewCount},
|
|
294
|
-
visualTheme,
|
|
295
|
-
} = shop
|
|
380
|
+
const {id, visualTheme} = shop
|
|
296
381
|
|
|
297
|
-
const
|
|
382
|
+
const handleClick = useCallback(() => {
|
|
298
383
|
if (!touchable) return
|
|
299
384
|
navigateToShop({shopId: id})
|
|
300
385
|
}, [navigateToShop, id, touchable])
|
|
301
386
|
|
|
302
|
-
const
|
|
303
|
-
() => getFeaturedImages(visualTheme, featuredImagesLimit),
|
|
304
|
-
[visualTheme, featuredImagesLimit]
|
|
305
|
-
)
|
|
306
|
-
|
|
307
|
-
const numberOfFeaturedImages = featuredImages?.length ?? 0
|
|
308
|
-
|
|
309
|
-
const logoAverageColor = visualTheme?.brandSettings?.colors?.logoAverage
|
|
310
|
-
const logoDominantColor = visualTheme?.brandSettings?.colors?.logoDominant
|
|
311
|
-
const logoColor = logoAverageColor || logoDominantColor
|
|
312
|
-
|
|
313
|
-
const logoBackgroundClassName = React.useMemo(
|
|
314
|
-
() => (logoColor && isDarkColor(logoColor) ? 'bg-white' : 'bg-gray-800'),
|
|
315
|
-
[logoColor]
|
|
316
|
-
)
|
|
317
|
-
|
|
318
|
-
const cardTheme = React.useMemo(
|
|
387
|
+
const cardTheme = useMemo(
|
|
319
388
|
() => extractBrandTheme(visualTheme?.brandSettings),
|
|
320
389
|
[visualTheme?.brandSettings]
|
|
321
390
|
)
|
|
322
391
|
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
cardTheme.type === 'coverImage' || cardTheme.type === 'brandColor'
|
|
392
|
+
const contextValue = useMemo<MerchantCardContextValue>(
|
|
393
|
+
() => ({
|
|
394
|
+
shop,
|
|
395
|
+
cardTheme,
|
|
396
|
+
touchable,
|
|
397
|
+
featuredImagesLimit,
|
|
398
|
+
onClick: handleClick,
|
|
399
|
+
}),
|
|
400
|
+
[shop, cardTheme, touchable, featuredImagesLimit, handleClick]
|
|
401
|
+
)
|
|
334
402
|
|
|
335
403
|
return (
|
|
336
|
-
<
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
fixedHeight ? 'h-[120px]' : 'flex-1'
|
|
348
|
-
)}
|
|
349
|
-
>
|
|
350
|
-
{hasBrandedHeader ? (
|
|
351
|
-
<MerchantCardBrandedHeader
|
|
352
|
-
shop={shop}
|
|
353
|
-
cardTheme={cardTheme}
|
|
354
|
-
logoBackgroundClassName={logoBackgroundClassName}
|
|
355
|
-
/>
|
|
356
|
-
) : (
|
|
357
|
-
<>
|
|
358
|
-
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-10">
|
|
359
|
-
<MerchantCardLogo
|
|
360
|
-
src={visualTheme?.logoImage?.url}
|
|
361
|
-
alt={`${name} logo`}
|
|
362
|
-
shopName={name}
|
|
363
|
-
className={logoBackgroundClassName}
|
|
364
|
-
data-testid="merchant-logo"
|
|
365
|
-
/>
|
|
366
|
-
</div>
|
|
367
|
-
|
|
368
|
-
{numberOfFeaturedImages > 0 ? (
|
|
369
|
-
featuredImages?.map((image, index) => (
|
|
370
|
-
<div
|
|
371
|
-
className="z-0 flex h-full flex-1"
|
|
372
|
-
key={image.url || index}
|
|
373
|
-
>
|
|
374
|
-
<MerchantCardImage
|
|
375
|
-
src={image.url}
|
|
376
|
-
alt={image.altText || ''}
|
|
377
|
-
thumbhash={image.thumbhash ?? undefined}
|
|
378
|
-
/>
|
|
379
|
-
</div>
|
|
380
|
-
))
|
|
381
|
-
) : (
|
|
382
|
-
<div
|
|
383
|
-
className="h-20 bg-gray-100"
|
|
384
|
-
data-testid="image-fallback"
|
|
385
|
-
/>
|
|
386
|
-
)}
|
|
387
|
-
</>
|
|
388
|
-
)}
|
|
389
|
-
</MerchantCardImageContainer>
|
|
390
|
-
|
|
391
|
-
<MerchantCardInfo className="flex items-center justify-between p-3">
|
|
392
|
-
<div className="flex flex-col items-start gap-2">
|
|
393
|
-
<div className="flex min-w-0 flex-1 flex-col">
|
|
394
|
-
<MerchantCardName
|
|
395
|
-
className={cn(
|
|
396
|
-
'line-clamp-1 overflow-hidden text-ellipsis',
|
|
397
|
-
textColor
|
|
398
|
-
)}
|
|
399
|
-
>
|
|
400
|
-
{name}
|
|
401
|
-
</MerchantCardName>
|
|
402
|
-
|
|
403
|
-
<MerchantCardRating
|
|
404
|
-
rating={averageRating}
|
|
405
|
-
reviewCount={reviewCount}
|
|
406
|
-
className={textColor}
|
|
407
|
-
/>
|
|
408
|
-
</div>
|
|
409
|
-
</div>
|
|
410
|
-
</MerchantCardInfo>
|
|
411
|
-
</MerchantCardRoot>
|
|
412
|
-
</div>
|
|
404
|
+
<MerchantCardContext.Provider value={contextValue}>
|
|
405
|
+
{children ?? (
|
|
406
|
+
<MerchantCardContainer>
|
|
407
|
+
<MerchantCardHeader withLogo />
|
|
408
|
+
<MerchantCardInfo>
|
|
409
|
+
<MerchantCardName />
|
|
410
|
+
<MerchantCardRating />
|
|
411
|
+
</MerchantCardInfo>
|
|
412
|
+
</MerchantCardContainer>
|
|
413
|
+
)}
|
|
414
|
+
</MerchantCardContext.Provider>
|
|
413
415
|
)
|
|
414
416
|
}
|
|
415
417
|
|
|
416
|
-
export
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
})
|
|
425
|
-
|
|
426
|
-
export {MerchantCard}
|
|
418
|
+
export {
|
|
419
|
+
MerchantCard,
|
|
420
|
+
MerchantCardContainer,
|
|
421
|
+
MerchantCardHeader,
|
|
422
|
+
MerchantCardInfo,
|
|
423
|
+
MerchantCardName,
|
|
424
|
+
MerchantCardRating,
|
|
425
|
+
}
|
|
@@ -8,8 +8,8 @@ import {useSavedProductsActions} from '../../hooks/user/useSavedProductsActions'
|
|
|
8
8
|
import {formatMoney} from '../../lib/formatMoney'
|
|
9
9
|
import {cn} from '../../lib/utils'
|
|
10
10
|
import {FavoriteButton} from '../atoms/favorite-button'
|
|
11
|
+
import {Image} from '../atoms/image'
|
|
11
12
|
import {ProductVariantPrice} from '../atoms/product-variant-price'
|
|
12
|
-
import {ThumbhashImage} from '../atoms/thumbhash-image'
|
|
13
13
|
import {Touchable} from '../atoms/touchable'
|
|
14
14
|
import {Badge} from '../ui/badge'
|
|
15
15
|
|
|
@@ -115,7 +115,7 @@ function ProductCardImage({className, ...props}: React.ComponentProps<'img'>) {
|
|
|
115
115
|
const renderImageElement = useCallback(
|
|
116
116
|
(src: string) => {
|
|
117
117
|
const imageElement = thumbhash ? (
|
|
118
|
-
<
|
|
118
|
+
<Image
|
|
119
119
|
data-slot="product-card-image"
|
|
120
120
|
src={src}
|
|
121
121
|
alt={alt}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
|
|
2
1
|
import {ContentWrapper} from '../atoms/content-wrapper'
|
|
2
|
+
import {Image} from '../atoms/image'
|
|
3
3
|
|
|
4
4
|
type ImageContentWrapperProps = (
|
|
5
5
|
| {publicId: string; externalId?: never}
|
|
@@ -26,14 +26,21 @@ export function ImageContentWrapper({
|
|
|
26
26
|
{({content, loading}) => {
|
|
27
27
|
if (loading) return Loader ? <>{Loader}</> : null
|
|
28
28
|
|
|
29
|
+
const aspectRatio =
|
|
30
|
+
content?.image?.width && content?.image?.height
|
|
31
|
+
? content.image.width / content.image.height
|
|
32
|
+
: undefined
|
|
33
|
+
|
|
29
34
|
return (
|
|
30
|
-
<
|
|
35
|
+
<Image
|
|
31
36
|
src={content?.image?.url}
|
|
37
|
+
thumbhash={content?.image?.thumbhash}
|
|
32
38
|
width={width}
|
|
33
39
|
height={height}
|
|
34
40
|
alt={content?.title}
|
|
35
41
|
onLoad={onLoad}
|
|
36
42
|
className={className}
|
|
43
|
+
aspectRatio={aspectRatio}
|
|
37
44
|
/>
|
|
38
45
|
)
|
|
39
46
|
}}
|
package/src/components/index.ts
CHANGED
|
@@ -16,7 +16,7 @@ export * from './navigation/transition-link'
|
|
|
16
16
|
export * from './atoms/button'
|
|
17
17
|
export * from './atoms/favorite-button'
|
|
18
18
|
export * from './atoms/icon-button'
|
|
19
|
-
export * from './atoms/
|
|
19
|
+
export * from './atoms/image'
|
|
20
20
|
export * from './atoms/touchable'
|
|
21
21
|
export * from './atoms/long-press-detector'
|
|
22
22
|
export * from './atoms/alert-dialog'
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
|
|
9
9
|
import {useHandleAction} from '../../internal/useHandleAction'
|
|
10
10
|
import {useShopActions} from '../../internal/useShopActions'
|
|
11
|
-
import {fileToDataUri} from '../../utils'
|
|
12
11
|
import {useImageUpload} from '../storage/useImageUpload'
|
|
13
12
|
|
|
14
13
|
interface CreateImageContentParams {
|
|
@@ -48,12 +47,7 @@ export const useCreateImageContent = (): UseCreateImageContentReturns => {
|
|
|
48
47
|
throw new Error('Invalid file type: must be an image')
|
|
49
48
|
}
|
|
50
49
|
|
|
51
|
-
const [uploadImageResult] = await uploadImage(
|
|
52
|
-
{
|
|
53
|
-
mimeType: image.type,
|
|
54
|
-
uri: await fileToDataUri(image),
|
|
55
|
-
},
|
|
56
|
-
])
|
|
50
|
+
const [uploadImageResult] = await uploadImage(image)
|
|
57
51
|
const uploadImageUrl = uploadImageResult.imageUrl
|
|
58
52
|
|
|
59
53
|
if (!uploadImageUrl) {
|