@shopify/shop-minis-react 0.0.11 → 0.0.13

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 (93) hide show
  1. package/dist/_virtual/debounce.js +8 -0
  2. package/dist/_virtual/debounce.js.map +1 -0
  3. package/dist/components/commerce/merchant-card.js +201 -0
  4. package/dist/components/commerce/merchant-card.js.map +1 -0
  5. package/dist/components/commerce/product-card.js +11 -9
  6. package/dist/components/commerce/product-card.js.map +1 -1
  7. package/dist/hooks/product/useCuratedProducts.js +20 -0
  8. package/dist/hooks/product/useCuratedProducts.js.map +1 -0
  9. package/dist/hooks/product/useProduct.js +24 -0
  10. package/dist/hooks/product/useProduct.js.map +1 -0
  11. package/dist/hooks/product/useProductMedia.js +24 -0
  12. package/dist/hooks/product/useProductMedia.js.map +1 -0
  13. package/dist/hooks/product/useProductSearch.js +44 -0
  14. package/dist/hooks/product/useProductSearch.js.map +1 -0
  15. package/dist/hooks/product/useProductVariants.js +21 -0
  16. package/dist/hooks/product/useProductVariants.js.map +1 -0
  17. package/dist/hooks/product/useProducts.js +17 -0
  18. package/dist/hooks/product/useProducts.js.map +1 -0
  19. package/dist/hooks/shop/useShop.js +17 -0
  20. package/dist/hooks/shop/useShop.js.map +1 -0
  21. package/dist/hooks/user/useFollowedShops.js +21 -0
  22. package/dist/hooks/user/useFollowedShops.js.map +1 -0
  23. package/dist/hooks/user/useRecentProducts.js +21 -0
  24. package/dist/hooks/user/useRecentProducts.js.map +1 -0
  25. package/dist/hooks/user/useRecentShops.js +21 -0
  26. package/dist/hooks/user/useRecentShops.js.map +1 -0
  27. package/dist/hooks/user/useSavedProducts.js +21 -0
  28. package/dist/hooks/user/useSavedProducts.js.map +1 -0
  29. package/dist/index.js +210 -178
  30. package/dist/index.js.map +1 -1
  31. package/dist/internal/useShopActionsPaginatedDataFetching.js +24 -24
  32. package/dist/internal/useShopActionsPaginatedDataFetching.js.map +1 -1
  33. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/_Symbol.js +12 -0
  34. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/_Symbol.js.map +1 -0
  35. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/_baseGetTag.js +17 -0
  36. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/_baseGetTag.js.map +1 -0
  37. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/_baseTrim.js +15 -0
  38. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/_baseTrim.js.map +1 -0
  39. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/_freeGlobal.js +12 -0
  40. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/_freeGlobal.js.map +1 -0
  41. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/_getRawTag.js +22 -0
  42. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/_getRawTag.js.map +1 -0
  43. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/_objectToString.js +14 -0
  44. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/_objectToString.js.map +1 -0
  45. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/_root.js +12 -0
  46. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/_root.js.map +1 -0
  47. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/_trimmedEndIndex.js +16 -0
  48. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/_trimmedEndIndex.js.map +1 -0
  49. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/debounce.js +61 -0
  50. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/debounce.js.map +1 -0
  51. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/isObject.js +14 -0
  52. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/isObject.js.map +1 -0
  53. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/isObjectLike.js +13 -0
  54. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/isObjectLike.js.map +1 -0
  55. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/isSymbol.js +16 -0
  56. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/isSymbol.js.map +1 -0
  57. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/now.js +14 -0
  58. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/now.js.map +1 -0
  59. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/toNumber.js +29 -0
  60. package/dist/node_modules/.pnpm/lodash@4.17.21/node_modules/lodash/toNumber.js.map +1 -0
  61. package/dist/types/minisSDK.generated.d.js.map +1 -1
  62. package/package.json +4 -3
  63. package/src/base.css +273 -0
  64. package/src/components/commerce/merchant-card.tsx +262 -0
  65. package/src/components/commerce/product-card.tsx +3 -1
  66. package/src/components/index.ts +1 -0
  67. package/src/hooks/index.ts +11 -0
  68. package/src/hooks/product/useCuratedProducts.doc.ts +32 -0
  69. package/src/hooks/product/useCuratedProducts.example.tsx +10 -0
  70. package/src/hooks/product/useCuratedProducts.ts +41 -0
  71. package/src/hooks/product/useProduct.ts +45 -0
  72. package/src/hooks/product/useProductMedia.ts +51 -0
  73. package/src/hooks/product/useProductSearch.doc.ts +32 -0
  74. package/src/hooks/product/useProductSearch.example.tsx +13 -0
  75. package/src/hooks/product/useProductSearch.ts +95 -0
  76. package/src/hooks/product/useProductVariants.ts +48 -0
  77. package/src/hooks/product/useProducts.ts +38 -0
  78. package/src/hooks/shop/useShop.doc.ts +31 -0
  79. package/src/hooks/shop/useShop.example.tsx +7 -0
  80. package/src/hooks/shop/useShop.ts +38 -0
  81. package/src/hooks/user/useFollowedShops.ts +43 -0
  82. package/src/hooks/user/useRecentProducts.doc.ts +32 -0
  83. package/src/hooks/user/useRecentProducts.example.tsx +13 -0
  84. package/src/hooks/user/useRecentProducts.ts +46 -0
  85. package/src/hooks/user/useRecentShops.ts +43 -0
  86. package/src/hooks/user/useSavedProducts.doc.ts +32 -0
  87. package/src/hooks/user/useSavedProducts.example.tsx +13 -0
  88. package/src/hooks/user/useSavedProducts.ts +46 -0
  89. package/src/index.css +1 -221
  90. package/src/internal/useShopActionsPaginatedDataFetching.ts +8 -8
  91. package/src/types/minisSDK.generated.d.ts +239 -4
  92. package/src/dev.tsx +0 -868
  93. package/src/mockActions.ts +0 -237
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shopify/shop-minis-react",
3
3
  "license": "SEE LICENSE IN LICENSE.txt",
4
- "version": "0.0.11",
4
+ "version": "0.0.13",
5
5
  "sideEffects": false,
6
6
  "type": "module",
7
7
  "engines": {
@@ -56,7 +56,8 @@
56
56
  "tailwindcss": "4.1.8",
57
57
  "tw-animate-css": "1.3.4",
58
58
  "url-parse": "1.5.10",
59
- "vaul": "1.1.2"
59
+ "vaul": "1.1.2",
60
+ "lodash": "4.17.21"
60
61
  },
61
62
  "devDependencies": {
62
63
  "@shopify/generate-docs": "^0.16.6",
@@ -88,7 +89,7 @@
88
89
  "directory": "packages/shop-minis-react"
89
90
  },
90
91
  "scripts": {
91
- "dev": "vite",
92
+ "dev": "vite ./demo",
92
93
  "build": "vite build --mode lib",
93
94
  "build:watch": "vite build --mode lib --watch",
94
95
  "type-check": "tsc --noEmit",
package/src/base.css ADDED
@@ -0,0 +1,273 @@
1
+ @import 'tailwindcss';
2
+ @import 'tw-animate-css';
3
+
4
+ @custom-variant dark (&:is(.dark *));
5
+
6
+ @theme inline {
7
+ --radius-sm: calc(var(--radius) - 4px);
8
+ --radius-md: calc(var(--radius) - 2px);
9
+ --radius-lg: var(--radius);
10
+ --radius-xl: calc(var(--radius) + 4px);
11
+ --color-background: var(--background);
12
+ --color-foreground: var(--foreground);
13
+ --color-card: var(--card);
14
+ --color-card-foreground: var(--card-foreground);
15
+ --color-popover: var(--popover);
16
+ --color-popover-foreground: var(--popover-foreground);
17
+ --color-primary: var(--primary);
18
+ --color-primary-foreground: var(--primary-foreground);
19
+ --color-secondary: var(--secondary);
20
+ --color-secondary-foreground: var(--secondary-foreground);
21
+ --color-muted: var(--muted);
22
+ --color-muted-foreground: var(--muted-foreground);
23
+ --color-accent: var(--accent);
24
+ --color-accent-foreground: var(--accent-foreground);
25
+ --color-destructive: var(--destructive);
26
+ --color-border: var(--border);
27
+ --color-input: var(--input);
28
+ --color-ring: var(--ring);
29
+ --color-chart-1: var(--chart-1);
30
+ --color-chart-2: var(--chart-2);
31
+ --color-chart-3: var(--chart-3);
32
+ --color-chart-4: var(--chart-4);
33
+ --color-chart-5: var(--chart-5);
34
+ --color-sidebar: var(--sidebar);
35
+ --color-sidebar-foreground: var(--sidebar-foreground);
36
+ --color-sidebar-primary: var(--sidebar-primary);
37
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
38
+ --color-sidebar-accent: var(--sidebar-accent);
39
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
40
+ --color-sidebar-border: var(--sidebar-border);
41
+ --color-sidebar-ring: var(--sidebar-ring);
42
+
43
+ /* Palette colors for Tailwind */
44
+ --color-purple-d80: var(--purple-d80);
45
+ --color-purple-d60: var(--purple-d60);
46
+ --color-purple-d50: var(--purple-d50);
47
+ --color-purple-p40: var(--purple-p40);
48
+ --color-purple-l30: var(--purple-l30);
49
+ --color-purple-l20: var(--purple-l20);
50
+ --color-purple-l10: var(--purple-l10);
51
+ --color-purple-l5: var(--purple-l5);
52
+ --color-purple-l2: var(--purple-l2);
53
+
54
+ --color-grayscale-d100: var(--grayscale-d100);
55
+ --color-grayscale-d93: var(--grayscale-d93);
56
+ --color-grayscale-d90: var(--grayscale-d90);
57
+ --color-grayscale-d80: var(--grayscale-d80);
58
+ --color-grayscale-d70: var(--grayscale-d70);
59
+ --color-grayscale-d60: var(--grayscale-d60);
60
+ --color-grayscale-d50: var(--grayscale-d50);
61
+ --color-grayscale-l40: var(--grayscale-l40);
62
+ --color-grayscale-l20: var(--grayscale-l20);
63
+ --color-grayscale-l10: var(--grayscale-l10);
64
+ --color-grayscale-l6: var(--grayscale-l6);
65
+ --color-grayscale-l5: var(--grayscale-l5);
66
+ --color-grayscale-l0: var(--grayscale-l0);
67
+
68
+ --color-green-d90: var(--green-d90);
69
+ --color-green-d80: var(--green-d80);
70
+ --color-green-d70: var(--green-d70);
71
+ --color-green-l30: var(--green-l30);
72
+ --color-green-l20: var(--green-l20);
73
+ --color-green-l10: var(--green-l10);
74
+ --color-green-l5: var(--green-l5);
75
+
76
+ --color-poppy-d80: var(--poppy-d80);
77
+ --color-poppy-d70: var(--poppy-d70);
78
+ --color-poppy-d50: var(--poppy-d50);
79
+ --color-poppy-l40: var(--poppy-l40);
80
+ --color-poppy-l20: var(--poppy-l20);
81
+ --color-poppy-l10: var(--poppy-l10);
82
+ --color-poppy-l4: var(--poppy-l4);
83
+
84
+ --color-ochre-d90: var(--ochre-d90);
85
+ --color-ochre-d70: var(--ochre-d70);
86
+ --color-ochre-d60: var(--ochre-d60);
87
+ --color-ochre-l50: var(--ochre-l50);
88
+ --color-ochre-l30: var(--ochre-l30);
89
+ --color-ochre-l20: var(--ochre-l20);
90
+ --color-ochre-l10: var(--ochre-l10);
91
+ --color-ochre-l6: var(--ochre-l6);
92
+
93
+ --color-brand-aqua: var(--brand-aqua);
94
+ --color-brand-violet: var(--brand-violet);
95
+ --color-brand-magenta: var(--brand-magenta);
96
+ --color-brand-olive: var(--brand-olive);
97
+ --color-brand-lime: var(--brand-lime);
98
+ --color-brand-sage: var(--brand-sage);
99
+ --color-brand-sand: var(--brand-sand);
100
+ }
101
+
102
+ :root {
103
+ --radius: 0.65rem;
104
+
105
+ /* Palette Colors - Purple */
106
+ --purple-d80: #1b163b;
107
+ --purple-d60: #322c7d;
108
+ --purple-d50: #4524db;
109
+ --purple-p40: #5433eb;
110
+ --purple-l30: #6445ed;
111
+ --purple-l20: #9c83f8;
112
+ --purple-l10: #dbd1ff;
113
+ --purple-l5: #eeeaff;
114
+ --purple-l2: #f7f5ff;
115
+
116
+ /* Palette Colors - Grayscale */
117
+ --grayscale-d100: #000000;
118
+ --grayscale-d93: #121212;
119
+ --grayscale-d90: #1a1a1a;
120
+ --grayscale-d80: #2a2a2a;
121
+ --grayscale-d70: #404040;
122
+ --grayscale-d60: #656667;
123
+ --grayscale-d50: #6f7071;
124
+ --grayscale-l40: #a6a8a9;
125
+ --grayscale-l20: #c9cbcc;
126
+ --grayscale-l10: #e1e4e5;
127
+ --grayscale-l6: #eef0f1;
128
+ --grayscale-l5: #f2f4f5;
129
+ --grayscale-l0: #ffffff;
130
+
131
+ /* Palette Colors - Green */
132
+ --green-d90: #002e24;
133
+ --green-d80: #004839;
134
+ --green-d70: #008552;
135
+ --green-l30: #92d08d;
136
+ --green-l20: #baebcb;
137
+ --green-l10: #d2f2de;
138
+ --green-l5: #e4f6eb;
139
+
140
+ /* Palette Colors - Poppy */
141
+ --poppy-d80: #481609;
142
+ --poppy-d70: #832711;
143
+ --poppy-d50: #d92a0f;
144
+ --poppy-l40: #f05d38;
145
+ --poppy-l20: #ff967d;
146
+ --poppy-l10: #ffd2c2;
147
+ --poppy-l4: #ffece9;
148
+
149
+ /* Palette Colors - Ochre */
150
+ --ochre-d90: #443600;
151
+ --ochre-d70: #8c6e01;
152
+ --ochre-d60: #c29d05;
153
+ --ochre-l50: #e3be2b;
154
+ --ochre-l30: #f8db67;
155
+ --ochre-l20: #ffec9f;
156
+ --ochre-l10: #fff4cb;
157
+ --ochre-l6: #fff9e2;
158
+
159
+ /* Palette Colors - Brand */
160
+ --brand-aqua: #8dc0c6;
161
+ --brand-violet: #a327c2;
162
+ --brand-magenta: #d354ff;
163
+ --brand-olive: #8b8f01;
164
+ --brand-lime: #c7de00;
165
+ --brand-sage: #d8e59d;
166
+ --brand-sand: #f4f4ed;
167
+
168
+ /* Semantic Colors */
169
+ --background: var(--grayscale-l0);
170
+ --foreground: var(--grayscale-d100);
171
+ --card: var(--grayscale-l0);
172
+ --card-foreground: var(--grayscale-d100);
173
+ --popover: var(--grayscale-l0);
174
+ --popover-foreground: var(--grayscale-d100);
175
+ --primary: var(--purple-p40);
176
+ --primary-foreground: var(--grayscale-l0);
177
+ --secondary: var(--grayscale-l5);
178
+ --secondary-foreground: var(--grayscale-d70);
179
+ --muted: var(--grayscale-l5);
180
+ --muted-foreground: var(--grayscale-d70);
181
+ --accent: var(--grayscale-l5);
182
+ --accent-foreground: var(--grayscale-d70);
183
+ --destructive: var(--poppy-d50);
184
+ --border: rgba(0, 0, 0, 0.1);
185
+ --input: var(--grayscale-l20);
186
+ --ring: var(--purple-p40);
187
+ --chart-1: var(--ochre-d90);
188
+ --chart-2: var(--green-d70);
189
+ --chart-3: var(--green-l30);
190
+ --chart-4: var(--grayscale-d50);
191
+ --chart-5: var(--green-l20);
192
+ --sidebar: var(--grayscale-l5);
193
+ --sidebar-foreground: var(--grayscale-d100);
194
+ --sidebar-primary: var(--purple-p40);
195
+ --sidebar-primary-foreground: var(--purple-l20);
196
+ --sidebar-accent: var(--grayscale-l5);
197
+ --sidebar-accent-foreground: var(--grayscale-d70);
198
+ --sidebar-border: rgba(0, 0, 0, 0.1);
199
+ --sidebar-ring: var(--purple-p40);
200
+ }
201
+
202
+ .dark {
203
+ /* Dark mode semantic colors */
204
+ --background: var(--grayscale-d93);
205
+ --foreground: var(--grayscale-l0);
206
+ --card: var(--grayscale-d93);
207
+ --card-foreground: var(--grayscale-l0);
208
+ --popover: var(--grayscale-d93);
209
+ --popover-foreground: var(--grayscale-l0);
210
+ --primary: var(--purple-l20);
211
+ --primary-foreground: var(--grayscale-d100);
212
+ --secondary: var(--grayscale-d80);
213
+ --secondary-foreground: var(--grayscale-l0);
214
+ --muted: var(--grayscale-d80);
215
+ --muted-foreground: var(--grayscale-l20);
216
+ --accent: var(--grayscale-d80);
217
+ --accent-foreground: var(--grayscale-l0);
218
+ --destructive: var(--poppy-l40);
219
+ --border: rgba(255, 255, 255, 0.2);
220
+ --input: var(--grayscale-l40);
221
+ --ring: var(--purple-l20);
222
+ --chart-1: var(--green-l30);
223
+ --chart-2: var(--grayscale-d60);
224
+ --chart-3: var(--green-l20);
225
+ --chart-4: var(--grayscale-d60);
226
+ --chart-5: var(--ochre-l30);
227
+ --sidebar: var(--grayscale-d80);
228
+ --sidebar-foreground: var(--grayscale-l0);
229
+ --sidebar-primary: var(--purple-l20);
230
+ --sidebar-primary-foreground: var(--purple-l10);
231
+ --sidebar-accent: var(--grayscale-d80);
232
+ --sidebar-accent-foreground: var(--grayscale-l0);
233
+ --sidebar-border: rgba(255, 255, 255, 0.2);
234
+ --sidebar-ring: var(--purple-l20);
235
+ }
236
+
237
+ @layer base {
238
+ * {
239
+ @apply border-border outline-ring/50;
240
+ }
241
+ body {
242
+ @apply bg-background text-foreground;
243
+ }
244
+ }
245
+
246
+ html,
247
+ body {
248
+ touch-action: manipulation;
249
+ }
250
+
251
+ /* Disable text selection */
252
+ body {
253
+ -webkit-user-select: none;
254
+ -moz-user-select: none;
255
+ -ms-user-select: none;
256
+ user-select: none;
257
+ }
258
+
259
+ /* Re-enable text selection for input fields */
260
+ input,
261
+ textarea {
262
+ -webkit-user-select: text;
263
+ -moz-user-select: text;
264
+ -ms-user-select: text;
265
+ user-select: text;
266
+ }
267
+
268
+ /* Disable image drag */
269
+ img,
270
+ .no-drag {
271
+ -webkit-user-drag: none;
272
+ user-drag: none;
273
+ }
@@ -0,0 +1,262 @@
1
+ import * as React from 'react'
2
+
3
+ import {cva, type VariantProps} from 'class-variance-authority'
4
+ import {Star} from 'lucide-react'
5
+ import {Slot as SlotPrimitive} from 'radix-ui'
6
+
7
+ import {useShopNavigation} from '../../hooks/navigation/useShopNavigation'
8
+ import {cn} from '../../lib/utils'
9
+ import {type Shop} from '../../types/minisSDK.generated.d'
10
+ import {Touchable} from '../atoms/touchable'
11
+
12
+ const merchantCardVariants = cva(
13
+ 'relative w-full aspect-square overflow-hidden rounded-xl border border-grayscale-l20 bg-grayscale-l0 flex flex-col',
14
+ {
15
+ variants: {
16
+ touchable: {
17
+ true: 'cursor-pointer',
18
+ false: '',
19
+ },
20
+ },
21
+ defaultVariants: {
22
+ touchable: true,
23
+ },
24
+ }
25
+ )
26
+
27
+ function formatReviewCount(count: number): string {
28
+ if (count >= 1000000) {
29
+ return `${Math.floor(count / 100000) / 10}M`
30
+ }
31
+ if (count >= 1000) {
32
+ return `${Math.floor(count / 1000)}K`
33
+ }
34
+ return count.toString()
35
+ }
36
+
37
+ function normalizeRating(rating: number): number {
38
+ return Math.round(rating * 10) / 10
39
+ }
40
+
41
+ export interface MerchantCardRootProps
42
+ extends React.ComponentProps<'div'>,
43
+ VariantProps<typeof merchantCardVariants> {
44
+ touchable?: boolean
45
+ asChild?: boolean
46
+ onPress?: () => void
47
+ }
48
+
49
+ function MerchantCardRoot({
50
+ className,
51
+ touchable = true,
52
+ asChild = false,
53
+ onPress,
54
+ ...props
55
+ }: MerchantCardRootProps) {
56
+ const Comp = asChild ? SlotPrimitive.Slot : 'div'
57
+
58
+ const content = (
59
+ <Comp
60
+ className={cn(merchantCardVariants({touchable}), className)}
61
+ {...props}
62
+ />
63
+ )
64
+
65
+ if (touchable && onPress) {
66
+ return (
67
+ <Touchable
68
+ onClick={onPress}
69
+ whileTap={{opacity: 0.7}}
70
+ transition={{
71
+ opacity: {type: 'tween', duration: 0.08, ease: 'easeInOut'},
72
+ }}
73
+ >
74
+ {content}
75
+ </Touchable>
76
+ )
77
+ }
78
+
79
+ return content
80
+ }
81
+
82
+ function MerchantCardImageContainer({
83
+ className,
84
+ ...props
85
+ }: React.ComponentProps<'div'>) {
86
+ return (
87
+ <div
88
+ data-slot="merchant-card-image-container"
89
+ className={cn('relative overflow-hidden w-full flex-grow', className)}
90
+ {...props}
91
+ />
92
+ )
93
+ }
94
+
95
+ function MerchantCardImage({
96
+ className,
97
+ src,
98
+ alt,
99
+ ...props
100
+ }: React.ComponentProps<'img'> & {
101
+ src?: string
102
+ alt?: string
103
+ }) {
104
+ return src ? (
105
+ <img
106
+ data-slot="merchant-card-image"
107
+ src={src}
108
+ alt={alt}
109
+ className={cn('w-full h-full object-cover', className)}
110
+ {...props}
111
+ />
112
+ ) : (
113
+ <div className="w-full h-full bg-grayscale-l10" />
114
+ )
115
+ }
116
+
117
+ function MerchantCardLogo({
118
+ className,
119
+ src,
120
+ alt,
121
+ ...props
122
+ }: React.ComponentProps<'div'> & {
123
+ src?: string
124
+ alt?: string
125
+ }) {
126
+ return (
127
+ <div
128
+ data-slot="merchant-card-logo"
129
+ className={cn(
130
+ 'absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2',
131
+ 'w-14 h-14 rounded-xl',
132
+ 'flex items-center justify-center overflow-hidden',
133
+ className
134
+ )}
135
+ {...props}
136
+ >
137
+ {src ? (
138
+ <img src={src} alt={alt} className="w-full h-full object-cover" />
139
+ ) : (
140
+ <div className="w-full h-full bg-grayscale-l20" />
141
+ )}
142
+ </div>
143
+ )
144
+ }
145
+
146
+ function MerchantCardInfo({className, ...props}: React.ComponentProps<'div'>) {
147
+ return (
148
+ <div
149
+ data-slot="merchant-card-info"
150
+ className={cn('p-3 space-y-1 flex-shrink-0', className)}
151
+ {...props}
152
+ />
153
+ )
154
+ }
155
+
156
+ function MerchantCardName({
157
+ className,
158
+ children,
159
+ ...props
160
+ }: React.ComponentProps<'h3'>) {
161
+ return (
162
+ <h3
163
+ data-slot="merchant-card-name"
164
+ className={cn(
165
+ 'text-sm font-medium text-grayscale-d100',
166
+ 'truncate overflow-hidden whitespace-nowrap text-ellipsis',
167
+ className
168
+ )}
169
+ {...props}
170
+ >
171
+ {children}
172
+ </h3>
173
+ )
174
+ }
175
+
176
+ function MerchantCardRating({
177
+ className,
178
+ rating,
179
+ reviewCount,
180
+ ...props
181
+ }: React.ComponentProps<'div'> & {
182
+ rating?: number | null
183
+ reviewCount?: number
184
+ }) {
185
+ if (!rating || !reviewCount) return null
186
+
187
+ return (
188
+ <div
189
+ data-slot="merchant-card-rating"
190
+ className={cn(
191
+ 'flex items-center gap-1 text-sm text-grayscale-d100',
192
+ className
193
+ )}
194
+ {...props}
195
+ >
196
+ <Star className="h-3.5 w-3.5 fill-current" />
197
+ <>
198
+ {normalizeRating(rating)} ({formatReviewCount(reviewCount)})
199
+ </>
200
+ </div>
201
+ )
202
+ }
203
+
204
+ export interface MerchantCardProps {
205
+ shop: Shop
206
+ touchable?: boolean
207
+ }
208
+
209
+ // Composed MerchantCard component
210
+ function MerchantCard({shop, touchable = true}: MerchantCardProps) {
211
+ const {navigateToShop} = useShopNavigation()
212
+
213
+ const {
214
+ id,
215
+ name,
216
+ logoImage,
217
+ reviewAnalytics: {averageRating, reviewCount},
218
+ } = shop
219
+
220
+ const handlePress = React.useCallback(() => {
221
+ if (!touchable) return
222
+ navigateToShop({shopId: id})
223
+ }, [navigateToShop, id, touchable])
224
+
225
+ return (
226
+ <MerchantCardRoot touchable={touchable} onPress={handlePress}>
227
+ <MerchantCardImageContainer>
228
+ {/* TODO: Add featured image */}
229
+ <MerchantCardImage src={undefined} alt={`${name} featured image`} />
230
+ <MerchantCardLogo src={logoImage?.url} alt={`${name} logo`} />
231
+ </MerchantCardImageContainer>
232
+
233
+ <MerchantCardInfo>
234
+ <MerchantCardName>{name}</MerchantCardName>
235
+ <MerchantCardRating rating={averageRating} reviewCount={reviewCount} />
236
+ </MerchantCardInfo>
237
+ </MerchantCardRoot>
238
+ )
239
+ }
240
+
241
+ // Export with Object.assign pattern
242
+ export const MerchantCardPrimitive = Object.assign(MerchantCardRoot, {
243
+ ImageContainer: MerchantCardImageContainer,
244
+ Image: MerchantCardImage,
245
+ Logo: MerchantCardLogo,
246
+ Info: MerchantCardInfo,
247
+ Name: MerchantCardName,
248
+ Rating: MerchantCardRating,
249
+ })
250
+
251
+ export {
252
+ // Composed component
253
+ MerchantCard,
254
+ // Primitive components for custom composition
255
+ MerchantCardRoot,
256
+ MerchantCardImageContainer,
257
+ MerchantCardImage,
258
+ MerchantCardLogo,
259
+ MerchantCardInfo,
260
+ MerchantCardName,
261
+ MerchantCardRating,
262
+ }
@@ -96,6 +96,8 @@ function ProductCardImageContainer({
96
96
  <div
97
97
  data-slot="product-card-image-container"
98
98
  className={cn(
99
+ // Ensure the product image is stretched to the full size of the container (can't use width/height: 100% because of flex)
100
+ 'flex justify-stretch items-stretch',
99
101
  'relative overflow-hidden rounded-xl border border-gray-200',
100
102
  'w-full aspect-square',
101
103
  variant === 'compact' ? 'min-h-[104px]' : 'min-h-[134px]',
@@ -116,7 +118,7 @@ function ProductCardImage({
116
118
  alt?: string
117
119
  }) {
118
120
  return (
119
- <div className="w-full h-full bg-gray-100 flex items-center justify-center">
121
+ <div className="bg-gray-100 flex items-center justify-center">
120
122
  {src ? (
121
123
  <img
122
124
  data-slot="product-card-image"
@@ -2,6 +2,7 @@ export * from './MinisContainer'
2
2
 
3
3
  export * from './commerce/product-card'
4
4
  export * from './commerce/product-link'
5
+ export * from './commerce/merchant-card'
5
6
 
6
7
  export * from './atoms/accordion'
7
8
  export * from './atoms/alert'
@@ -1,5 +1,9 @@
1
1
  // - User Hooks
2
+ export * from './user/useRecentProducts'
3
+ export * from './user/useRecentShops'
4
+ export * from './user/useSavedProducts'
2
5
  export * from './user/useSavedProductsActions'
6
+ export * from './user/useFollowedShops'
3
7
  export * from './user/useFollowedShopsActions'
4
8
  export * from './user/useCurrentUser'
5
9
  export * from './user/useOrders'
@@ -9,8 +13,14 @@ export * from './user/useBuyerAttributes'
9
13
  export * from './product/useProductListActions'
10
14
  export * from './product/useProductLists'
11
15
  export * from './product/useProductList'
16
+ export * from './product/useProduct'
17
+ export * from './product/useProducts'
18
+ export * from './product/useProductVariants'
19
+ export * from './product/useProductMedia'
20
+ export * from './product/useProductSearch'
12
21
  export * from './product/useRecommendedProducts'
13
22
  export * from './product/usePopularProducts'
23
+ export * from './product/useCuratedProducts'
14
24
 
15
25
  // - Storage Hooks
16
26
  export * from './storage/useAsyncStorage'
@@ -23,6 +33,7 @@ export * from './navigation/useCloseMini'
23
33
  export * from './navigation/useDeeplink'
24
34
 
25
35
  // - Shop Hooks
36
+ export * from './shop/useShop'
26
37
  export * from './shop/useShopCartActions'
27
38
  export * from './shop/useRecommendedShops'
28
39
 
@@ -0,0 +1,32 @@
1
+ import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs'
2
+
3
+ const data: ReferenceEntityTemplateSchema = {
4
+ name: 'useCuratedProducts',
5
+ category: 'hooks',
6
+ subCategory: 'product',
7
+ isVisualComponent: false,
8
+ related: [],
9
+ description:
10
+ 'The `useCuratedProducts` hook enables fetching lists of products curated by the Shop team.',
11
+ type: 'hook',
12
+ defaultExample: {
13
+ codeblock: {
14
+ tabs: [
15
+ {
16
+ code: './useCuratedProducts.example.tsx',
17
+ language: 'tsx',
18
+ },
19
+ ],
20
+ title: 'Example code',
21
+ },
22
+ },
23
+ definitions: [
24
+ {
25
+ title: '',
26
+ type: 'UseCuratedProductsGeneratedType',
27
+ description: '',
28
+ },
29
+ ],
30
+ }
31
+
32
+ export default data
@@ -0,0 +1,10 @@
1
+ import {useCuratedProducts} from '@shopify/shop-minis-react'
2
+
3
+ export default function MyComponent() {
4
+ const {products, loading, error} = useCuratedProducts({
5
+ handle: 'ar-models',
6
+ requiredTags: ['furniture'],
7
+ })
8
+
9
+ console.log({products, loading, error})
10
+ }
@@ -0,0 +1,41 @@
1
+ import {useShopActions} from '../../internal/useShopActions'
2
+ import {useShopActionsPaginatedDataFetching} from '../../internal/useShopActionsPaginatedDataFetching'
3
+ import {
4
+ Product,
5
+ PaginatedDataHookOptionsBase,
6
+ PaginatedDataHookReturnsBase,
7
+ } from '../../types'
8
+
9
+ interface UseCuratedProductsParams extends PaginatedDataHookOptionsBase {
10
+ handle: string
11
+ requiredTags?: string[]
12
+ anyOfTags?: string[]
13
+ }
14
+
15
+ interface UseCuratedProductsReturns extends PaginatedDataHookReturnsBase {
16
+ products: Product[] | null
17
+ }
18
+
19
+ /**
20
+ * @param options - The options for the query
21
+ */
22
+ export const useCuratedProducts = (
23
+ params: UseCuratedProductsParams
24
+ ): UseCuratedProductsReturns => {
25
+ const {getCuratedProducts} = useShopActions()
26
+ const {skip, ...shopActionParams} = params ?? {}
27
+
28
+ const {data, ...rest} = useShopActionsPaginatedDataFetching(
29
+ getCuratedProducts,
30
+ shopActionParams,
31
+ {
32
+ skip,
33
+ hook: 'useCuratedProducts',
34
+ }
35
+ )
36
+
37
+ return {
38
+ ...rest,
39
+ products: data,
40
+ }
41
+ }