@shopify/shop-minis-react 0.0.18 → 0.0.20

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 (120) hide show
  1. package/dist/_virtual/index2.js +2 -3
  2. package/dist/_virtual/index2.js.map +1 -1
  3. package/dist/_virtual/index3.js +3 -2
  4. package/dist/_virtual/index3.js.map +1 -1
  5. package/dist/components/MinisContainer.js +13 -11
  6. package/dist/components/MinisContainer.js.map +1 -1
  7. package/dist/components/atoms/alert-dialog.js +41 -0
  8. package/dist/components/atoms/alert-dialog.js.map +1 -0
  9. package/dist/components/atoms/list.js +52 -0
  10. package/dist/components/atoms/list.js.map +1 -0
  11. package/dist/components/atoms/pagination.js +10 -0
  12. package/dist/components/atoms/pagination.js.map +1 -0
  13. package/dist/components/atoms/thumbhash-image.js +54 -0
  14. package/dist/components/atoms/thumbhash-image.js.map +1 -0
  15. package/dist/components/atoms/tracking-pixel.js +32 -0
  16. package/dist/components/atoms/tracking-pixel.js.map +1 -0
  17. package/dist/components/commerce/merchant-card-skeleton.js +29 -0
  18. package/dist/components/commerce/merchant-card-skeleton.js.map +1 -0
  19. package/dist/components/commerce/merchant-card.js +28 -22
  20. package/dist/components/commerce/merchant-card.js.map +1 -1
  21. package/dist/components/commerce/product-card-skeleton.js +20 -0
  22. package/dist/components/commerce/product-card-skeleton.js.map +1 -0
  23. package/dist/components/commerce/product-card.js +105 -78
  24. package/dist/components/commerce/product-card.js.map +1 -1
  25. package/dist/components/navigation/transition-container.js +8 -0
  26. package/dist/components/navigation/transition-container.js.map +1 -0
  27. package/dist/components/navigation/transition-link.js +27 -0
  28. package/dist/components/navigation/transition-link.js.map +1 -0
  29. package/dist/components/ui/skeleton.js +16 -0
  30. package/dist/components/ui/skeleton.js.map +1 -0
  31. package/dist/hooks/navigation/useNavigateWithTransition.js +43 -0
  32. package/dist/hooks/navigation/useNavigateWithTransition.js.map +1 -0
  33. package/dist/hooks/navigation/useViewTransitions.js +45 -0
  34. package/dist/hooks/navigation/useViewTransitions.js.map +1 -0
  35. package/dist/index.js +217 -196
  36. package/dist/index.js.map +1 -1
  37. package/dist/internal/useShopActions.js.map +1 -1
  38. package/dist/internal/useShopActionsDataFetching.js +26 -26
  39. package/dist/internal/useShopActionsDataFetching.js.map +1 -1
  40. package/dist/internal/useShopActionsPaginatedDataFetching.js.map +1 -1
  41. package/dist/mocks.js +267 -0
  42. package/dist/mocks.js.map +1 -0
  43. package/dist/shop-minis-react/node_modules/.pnpm/@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/assertThisInitialized.js +8 -0
  44. package/dist/shop-minis-react/node_modules/.pnpm/@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/assertThisInitialized.js.map +1 -0
  45. package/dist/shop-minis-react/node_modules/.pnpm/@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/extends.js +13 -0
  46. package/dist/shop-minis-react/node_modules/.pnpm/@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/extends.js.map +1 -0
  47. package/dist/shop-minis-react/node_modules/.pnpm/@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/inheritsLoose.js +8 -0
  48. package/dist/shop-minis-react/node_modules/.pnpm/@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/inheritsLoose.js.map +1 -0
  49. package/dist/shop-minis-react/node_modules/.pnpm/@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js +9 -0
  50. package/dist/shop-minis-react/node_modules/.pnpm/@babel_runtime@7.27.6/node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js.map +1 -0
  51. 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
  52. package/dist/shop-minis-react/node_modules/.pnpm/js-base64@3.7.7/node_modules/js-base64/base64.js +21 -0
  53. package/dist/shop-minis-react/node_modules/.pnpm/js-base64@3.7.7/node_modules/js-base64/base64.js.map +1 -0
  54. package/dist/shop-minis-react/node_modules/.pnpm/memoize-one@5.2.1/node_modules/memoize-one/dist/memoize-one.esm.js +28 -0
  55. package/dist/shop-minis-react/node_modules/.pnpm/memoize-one@5.2.1/node_modules/memoize-one/dist/memoize-one.esm.js.map +1 -0
  56. package/dist/shop-minis-react/node_modules/.pnpm/querystringify@2.2.0/node_modules/querystringify/index.js +1 -1
  57. package/dist/shop-minis-react/node_modules/.pnpm/react-intersection-observer@9.16.0_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/react-intersection-observer/dist/index.js +135 -0
  58. package/dist/shop-minis-react/node_modules/.pnpm/react-intersection-observer@9.16.0_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/react-intersection-observer/dist/index.js.map +1 -0
  59. package/dist/shop-minis-react/node_modules/.pnpm/react-router@7.7.0_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/react-router/dist/development/chunk-EF7DTUVF.js +1298 -0
  60. package/dist/shop-minis-react/node_modules/.pnpm/react-router@7.7.0_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/react-router/dist/development/chunk-EF7DTUVF.js.map +1 -0
  61. package/dist/shop-minis-react/node_modules/.pnpm/react-window@1.8.11_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/react-window/dist/index.esm.js +375 -0
  62. package/dist/shop-minis-react/node_modules/.pnpm/react-window@1.8.11_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/react-window/dist/index.esm.js.map +1 -0
  63. package/dist/shop-minis-react/node_modules/.pnpm/thumbhash@0.1.1/node_modules/thumbhash/thumbhash.js +145 -0
  64. package/dist/shop-minis-react/node_modules/.pnpm/thumbhash@0.1.1/node_modules/thumbhash/thumbhash.js.map +1 -0
  65. package/dist/shop-minis-react.css +1 -0
  66. package/dist/types/index.js +10 -0
  67. package/dist/types/index.js.map +1 -0
  68. package/dist/utils/image.js +15 -0
  69. package/dist/utils/image.js.map +1 -0
  70. package/package.json +18 -3
  71. package/src/components/MinisContainer.tsx +4 -1
  72. package/src/components/atoms/alert-dialog.tsx +67 -0
  73. package/src/components/atoms/list.tsx +101 -0
  74. package/src/components/atoms/pagination.tsx +19 -0
  75. package/src/components/atoms/thumbhash-image.tsx +66 -0
  76. package/src/components/atoms/tracking-pixel.tsx +40 -0
  77. package/src/components/commerce/merchant-card-skeleton.tsx +31 -0
  78. package/src/components/commerce/merchant-card.tsx +5 -2
  79. package/src/components/commerce/product-card-skeleton.tsx +30 -0
  80. package/src/components/commerce/product-card.tsx +49 -8
  81. package/src/components/index.ts +9 -0
  82. package/src/components/navigation/transition-container.tsx +7 -0
  83. package/src/components/navigation/transition-link.tsx +48 -0
  84. package/src/components/ui/skeleton.tsx +13 -0
  85. package/src/hooks/index.ts +1 -0
  86. package/src/hooks/navigation/useNavigateWithTransition.ts +62 -0
  87. package/src/hooks/navigation/useViewTransitions.ts +79 -0
  88. package/src/index.css +2 -0
  89. package/src/internal/useShopActions.ts +1 -1
  90. package/src/internal/useShopActionsDataFetching.ts +6 -7
  91. package/src/internal/useShopActionsPaginatedDataFetching.ts +3 -6
  92. package/src/mocks.ts +27 -9
  93. package/src/stories/Accordion.stories.tsx +124 -0
  94. package/src/stories/Alert.stories.tsx +38 -0
  95. package/src/stories/AlertDialog.stories.tsx +48 -0
  96. package/src/stories/Avatar.stories.tsx +29 -0
  97. package/src/stories/Badge.stories.tsx +46 -0
  98. package/src/stories/Button.stories.tsx +81 -0
  99. package/src/stories/Card.stories.tsx +40 -0
  100. package/src/stories/Checkbox.stories.tsx +44 -0
  101. package/src/stories/FavoriteButton.stories.tsx +58 -0
  102. package/src/stories/IconButton.stories.tsx +68 -0
  103. package/src/stories/Input.stories.tsx +44 -0
  104. package/src/stories/Label.stories.tsx +19 -0
  105. package/src/stories/List.stories.tsx +68 -0
  106. package/src/stories/MerchantCard.stories.tsx +55 -0
  107. package/src/stories/ProductCard.stories.tsx +85 -0
  108. package/src/stories/ProductLink.stories.tsx +46 -0
  109. package/src/stories/Progress.stories.tsx +30 -0
  110. package/src/stories/RadioGroup.stories.tsx +51 -0
  111. package/src/stories/Select.stories.tsx +85 -0
  112. package/src/stories/Skeleton.stories.tsx +19 -0
  113. package/src/stories/Toaster.stories.tsx +46 -0
  114. package/src/stories/Touchable.stories.tsx +40 -0
  115. package/src/styles/animations.css +90 -0
  116. package/src/styles/globals.css +8 -0
  117. package/src/styles/theme.css +1 -1
  118. package/src/styles/utilities.css +22 -0
  119. package/src/types/index.ts +7 -1
  120. package/src/utils/image.ts +18 -0
@@ -0,0 +1,30 @@
1
+ import * as React from 'react'
2
+
3
+ import {cn} from '../../lib/utils'
4
+ import {Skeleton} from '../ui/skeleton'
5
+
6
+ interface ProductCardSkeletonProps extends React.ComponentProps<'div'> {
7
+ variant?: 'default' | 'priceOverlay' | 'compact'
8
+ }
9
+
10
+ function ProductCardSkeleton({
11
+ className,
12
+ variant = 'default',
13
+ ...props
14
+ }: ProductCardSkeletonProps) {
15
+ return (
16
+ <div className={cn('relative w-full', className)} {...props}>
17
+ <div className="aspect-square w-full overflow-hidden rounded-xl border border-gray-100">
18
+ <Skeleton className="h-full w-full" />
19
+ </div>
20
+ {variant === 'default' ? (
21
+ <div className="mt-3 mb-3">
22
+ <Skeleton className="mb-2 h-4 w-2/3" />
23
+ <Skeleton className="mb-2 h-5 w-1/3" />
24
+ </div>
25
+ ) : null}
26
+ </div>
27
+ )
28
+ }
29
+
30
+ export {ProductCardSkeleton}
@@ -1,8 +1,8 @@
1
1
  import * as React from 'react'
2
+ import {useCallback} from 'react'
2
3
 
3
4
  import {type Product, type ProductVariant} from '@shopify/shop-minis-platform'
4
5
  import {cva, type VariantProps} from 'class-variance-authority'
5
- import {Heart} from 'lucide-react'
6
6
  import {Slot as SlotPrimitive} from 'radix-ui'
7
7
 
8
8
  import {useShopNavigation} from '../../hooks/navigation/useShopNavigation'
@@ -10,6 +10,7 @@ import {useSavedProductsActions} from '../../hooks/user/useSavedProductsActions'
10
10
  import {formatMoney} from '../../lib/formatMoney'
11
11
  import {cn} from '../../lib/utils'
12
12
  import {FavoriteButton} from '../atoms/favorite-button'
13
+ import {ThumbhashImage} from '../atoms/thumbhash-image'
13
14
  import {Touchable} from '../atoms/touchable'
14
15
  import {Badge} from '../ui/badge'
15
16
 
@@ -96,7 +97,7 @@ function ProductCardImageContainer({
96
97
  // Ensure the product image is stretched to the full size of the container (can't use width/height: 100% because of flex)
97
98
  'flex justify-stretch items-stretch',
98
99
  'relative overflow-hidden rounded-xl border border-gray-200',
99
- 'w-full aspect-square',
100
+ 'w-full aspect-square',
100
101
  variant === 'compact' ? 'min-h-[104px]' : 'min-h-[134px]',
101
102
  className
102
103
  )}
@@ -109,23 +110,50 @@ function ProductCardImage({
109
110
  className,
110
111
  src,
111
112
  alt,
113
+ aspectRatio,
114
+ thumbhash,
112
115
  ...props
113
116
  }: React.ComponentProps<'img'> & {
114
117
  src?: string
115
118
  alt?: string
119
+ aspectRatio?: number
120
+ thumbhash?: string
116
121
  }) {
117
- return (
118
- <div className="bg-gray-100 flex items-center justify-center">
119
- {src ? (
122
+ const renderImageElement = useCallback(
123
+ (src: string) => {
124
+ if (thumbhash) {
125
+ return (
126
+ <ThumbhashImage
127
+ data-slot="product-card-image"
128
+ src={src}
129
+ alt={alt}
130
+ aspectRatio={aspectRatio}
131
+ thumbhash={thumbhash}
132
+ className={cn('w-full h-full object-cover', className)}
133
+ {...props}
134
+ />
135
+ )
136
+ }
137
+
138
+ return (
120
139
  <img
121
140
  data-slot="product-card-image"
122
141
  src={src}
123
142
  alt={alt}
124
- className={cn('w-full h-full object-cover', className)}
143
+ className={cn('w-full h-full', className)}
125
144
  {...props}
126
145
  />
146
+ )
147
+ },
148
+ [alt, aspectRatio, className, props, thumbhash]
149
+ )
150
+
151
+ return (
152
+ <div className="bg-gray-100 flex items-center justify-center w-full h-full">
153
+ {src ? (
154
+ renderImageElement(src)
127
155
  ) : (
128
- <div className="text-gray-400 text-sm">No Image</div>
156
+ <div className="text-gray-400 text-sm w-full text-center">No Image</div>
129
157
  )}
130
158
  </div>
131
159
  )
@@ -249,13 +277,21 @@ function ProductCardOriginalPrice({
249
277
  }
250
278
 
251
279
  export interface ProductCardProps {
280
+ /** The product to display in the card */
252
281
  product: Product
282
+ /** Optional selected variant of the product to show specific variant data */
253
283
  selectedProductVariant?: ProductVariant
284
+ /** Visual style variant of the card */
254
285
  variant?: 'default' | 'priceOverlay' | 'compact'
286
+ /** Whether the card can be clicked/tapped to navigate to product details */
255
287
  touchable?: boolean
288
+ /** Optional text to display in a badge on the card */
256
289
  badgeText?: string
290
+ /** Visual style variant for the badge */
257
291
  badgeVariant?: 'default' | 'secondary' | 'destructive' | 'outline'
292
+ /** Callback fired when the favorite button is toggled */
258
293
  onFavoriteToggled?: (isFavorited: boolean) => void
294
+ /** Optional ID for the section containing this card */
259
295
  sectionId?: string
260
296
  }
261
297
 
@@ -351,7 +387,12 @@ function ProductCard({
351
387
  onPress={handlePress}
352
388
  >
353
389
  <ProductCardImageContainer variant={variant}>
354
- <ProductCardImage src={imageUrl} alt={imageAltText} />
390
+ <ProductCardImage
391
+ src={imageUrl}
392
+ alt={imageAltText}
393
+ aspectRatio={1}
394
+ thumbhash={featuredImage?.thumbhash ?? undefined}
395
+ />
355
396
 
356
397
  {/* Price overlay badge for priceOverlay variant */}
357
398
  {variant === 'priceOverlay' && currencyCode && amount && (
@@ -3,11 +3,19 @@ export * from './MinisContainer'
3
3
  export * from './commerce/product-card'
4
4
  export * from './commerce/product-link'
5
5
  export * from './commerce/merchant-card'
6
+ export * from './commerce/product-card-skeleton'
7
+ export * from './commerce/merchant-card-skeleton'
8
+
9
+ export * from './navigation/transition-container'
10
+ export * from './navigation/transition-link'
6
11
 
7
12
  export * from './atoms/button'
8
13
  export * from './atoms/favorite-button'
9
14
  export * from './atoms/icon-button'
15
+ export * from './atoms/thumbhash-image'
10
16
  export * from './atoms/touchable'
17
+ export * from './atoms/alert-dialog'
18
+ export * from './atoms/list'
11
19
 
12
20
  export * from './ui/accordion'
13
21
  export * from './ui/alert'
@@ -28,3 +36,4 @@ export * from './ui/select'
28
36
  export * from './ui/separator'
29
37
  export * from './ui/sheet'
30
38
  export * from './ui/sonner'
39
+ export * from './ui/skeleton'
@@ -0,0 +1,7 @@
1
+ import {useViewTransitions} from '../../hooks/navigation/useViewTransitions'
2
+
3
+ export function TransitionContainer({children}: {children: React.ReactNode}) {
4
+ useViewTransitions()
5
+
6
+ return children
7
+ }
@@ -0,0 +1,48 @@
1
+ import {forwardRef, AnchorHTMLAttributes} from 'react'
2
+
3
+ import {useHref} from 'react-router'
4
+
5
+ import {useNavigateWithTransition} from '../../hooks/navigation/useNavigateWithTransition'
6
+
7
+ type TransitionLinkProps = AnchorHTMLAttributes<HTMLAnchorElement> & {
8
+ to: string
9
+ }
10
+
11
+ const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i
12
+
13
+ export const TransitionLink = forwardRef<
14
+ HTMLAnchorElement,
15
+ TransitionLinkProps
16
+ >(({onClick, to, children, ...props}, forwardedRef) => {
17
+ const transitionNavigate = useNavigateWithTransition()
18
+
19
+ const isAbsolute = typeof to === 'string' && ABSOLUTE_URL_REGEX.test(to)
20
+
21
+ if (isAbsolute) {
22
+ console.warn(
23
+ `TransitionLink: absolute URLs are not supported. Please update to a valid relative path.`
24
+ )
25
+ }
26
+
27
+ const href = useHref(to)
28
+
29
+ const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
30
+ if (onClick) onClick(event)
31
+
32
+ if (!event.defaultPrevented) {
33
+ event.preventDefault()
34
+ transitionNavigate(to)
35
+ }
36
+ }
37
+
38
+ return (
39
+ <a
40
+ {...props}
41
+ onClick={handleClick}
42
+ href={isAbsolute ? undefined : href}
43
+ ref={forwardedRef}
44
+ >
45
+ {children}
46
+ </a>
47
+ )
48
+ })
@@ -0,0 +1,13 @@
1
+ import {cn} from '../../lib/utils'
2
+
3
+ function Skeleton({className, ...props}: React.ComponentProps<'div'>) {
4
+ return (
5
+ <div
6
+ data-slot="skeleton"
7
+ className={cn('bg-accent animate-pulse rounded-md', className)}
8
+ {...props}
9
+ />
10
+ )
11
+ }
12
+
13
+ export {Skeleton}
@@ -31,6 +31,7 @@ export * from './storage/useImageUpload'
31
31
  export * from './navigation/useShopNavigation'
32
32
  export * from './navigation/useCloseMini'
33
33
  export * from './navigation/useDeeplink'
34
+ export * from './navigation/useNavigateWithTransition'
34
35
 
35
36
  // - Shop Hooks
36
37
  export * from './shop/useShop'
@@ -0,0 +1,62 @@
1
+ import {useLocation, useNavigate, NavigateOptions} from 'react-router'
2
+
3
+ import {DATA_NAVIGATION_TYPE_ATTRIBUTE} from '../../types'
4
+
5
+ export function useNavigateWithTransition() {
6
+ const navigate = useNavigate()
7
+ const location = useLocation()
8
+
9
+ const transitionNavigate = (
10
+ to: string | number,
11
+ options?: NavigateOptions
12
+ ) => {
13
+ if (typeof to === 'number') {
14
+ // Delta navigation - no options parameter
15
+ if (document.startViewTransition) {
16
+ const transition = document.startViewTransition(() => {
17
+ navigate(to)
18
+ })
19
+
20
+ transition.finished
21
+ .then(() => {
22
+ document.documentElement.removeAttribute(
23
+ DATA_NAVIGATION_TYPE_ATTRIBUTE
24
+ )
25
+ })
26
+ .catch(error => {
27
+ console.error('View transition error:', error)
28
+ })
29
+ } else {
30
+ return navigate(to)
31
+ }
32
+ return
33
+ }
34
+
35
+ const isSameRoute = to === location.pathname
36
+
37
+ // Path navigation - with options
38
+ if (document.startViewTransition) {
39
+ const transition = document.startViewTransition(() => {
40
+ navigate(to, {
41
+ preventScrollReset: true,
42
+ replace: isSameRoute,
43
+ ...options,
44
+ })
45
+ })
46
+
47
+ transition.finished
48
+ .then(() => {
49
+ document.documentElement.removeAttribute(
50
+ DATA_NAVIGATION_TYPE_ATTRIBUTE
51
+ )
52
+ })
53
+ .catch(error => {
54
+ console.error('View transition error:', error)
55
+ })
56
+ } else {
57
+ return navigate(to, options)
58
+ }
59
+ }
60
+
61
+ return transitionNavigate
62
+ }
@@ -0,0 +1,79 @@
1
+ import {useEffect} from 'react'
2
+
3
+ import {useNavigationType, useLocation} from 'react-router'
4
+
5
+ import {DATA_NAVIGATION_TYPE_ATTRIBUTE, NAVIGATION_TYPES} from '../../types'
6
+
7
+ export function useViewTransitions() {
8
+ const location = useLocation()
9
+ const navType = useNavigationType()
10
+
11
+ useEffect(() => {
12
+ let isAndroidBackPress = false
13
+
14
+ const handleAndroidBackPress = () => {
15
+ isAndroidBackPress = true
16
+
17
+ if (document.startViewTransition) {
18
+ const transition = document.startViewTransition(() => {
19
+ document.documentElement.setAttribute(
20
+ DATA_NAVIGATION_TYPE_ATTRIBUTE,
21
+ NAVIGATION_TYPES.backward
22
+ )
23
+ })
24
+
25
+ transition.finished
26
+ .then(() => {
27
+ document.documentElement.removeAttribute(
28
+ DATA_NAVIGATION_TYPE_ATTRIBUTE
29
+ )
30
+ })
31
+ .catch(error => {
32
+ console.error('View transition error:', error)
33
+ })
34
+ }
35
+ }
36
+
37
+ const handlePopstate = (event: PopStateEvent) => {
38
+ // If the ios back gesture is used, we don't want to trigger view transition
39
+ if (event.hasUAVisualTransition && !isAndroidBackPress) {
40
+ document.documentElement.setAttribute(
41
+ DATA_NAVIGATION_TYPE_ATTRIBUTE,
42
+ NAVIGATION_TYPES.none
43
+ )
44
+ }
45
+ }
46
+
47
+ window.addEventListener('androidbackpressed', handleAndroidBackPress)
48
+ window.addEventListener('popstate', handlePopstate)
49
+
50
+ return () => {
51
+ window.removeEventListener('popstate', handlePopstate)
52
+ window.removeEventListener('androidbackpressed', handleAndroidBackPress)
53
+ }
54
+ }, [location])
55
+
56
+ useEffect(() => {
57
+ const currentNavType = document.documentElement.getAttribute(
58
+ DATA_NAVIGATION_TYPE_ATTRIBUTE
59
+ )
60
+
61
+ if (!currentNavType) {
62
+ if (navType === 'PUSH') {
63
+ document.documentElement.setAttribute(
64
+ DATA_NAVIGATION_TYPE_ATTRIBUTE,
65
+ NAVIGATION_TYPES.forward
66
+ )
67
+ } else if (navType === 'POP') {
68
+ document.documentElement.setAttribute(
69
+ DATA_NAVIGATION_TYPE_ATTRIBUTE,
70
+ NAVIGATION_TYPES.backward
71
+ )
72
+ }
73
+ }
74
+
75
+ return () => {
76
+ document.documentElement.removeAttribute(DATA_NAVIGATION_TYPE_ATTRIBUTE)
77
+ }
78
+ }, [navType, location])
79
+ }
package/src/index.css CHANGED
@@ -3,6 +3,8 @@
3
3
 
4
4
  @import './styles/theme.css';
5
5
  @import './styles/globals.css';
6
+ @import './styles/animations.css';
7
+ @import './styles/utilities.css';
6
8
 
7
9
  /* When consumed as npm package, this path resolves from the consuming app's location */
8
10
  @source '../node_modules/@shopify/shop-minis-react/src';
@@ -1,4 +1,4 @@
1
- import {ShopActions} from '../types'
1
+ import {ShopActions} from '@shopify/shop-minis-platform/actions'
2
2
 
3
3
  export function useShopActions(): ShopActions {
4
4
  return window.minisSDK
@@ -41,13 +41,12 @@ export const useShopActionsDataFetching = <
41
41
  validator?.(dataToValidate)
42
42
  return null
43
43
  } catch (err) {
44
- return (
45
- err ??
46
- new MiniError({
47
- hook,
48
- message: 'Validation failed',
49
- })
50
- )
44
+ if (err instanceof Error) return err
45
+
46
+ return new MiniError({
47
+ hook,
48
+ message: 'Validation failed',
49
+ })
51
50
  }
52
51
  },
53
52
  [validator, hook]
@@ -1,11 +1,8 @@
1
1
  import {useCallback, useEffect, useMemo, useState} from 'react'
2
2
 
3
- import {
4
- DataHookFetchPolicy,
5
- PaginatedDataHookReturnsBase,
6
- PaginationInfo,
7
- ShopAction,
8
- } from '../types'
3
+ import {ShopAction, PaginationInfo} from '@shopify/shop-minis-platform/actions'
4
+
5
+ import {DataHookFetchPolicy, PaginatedDataHookReturnsBase} from '../types'
9
6
  import {formatError, MiniError} from '../utils/errors'
10
7
 
11
8
  export interface ShopActionsDataFetchingResult<S>
package/src/mocks.ts CHANGED
@@ -2,7 +2,7 @@ import {Product, Gender} from '@shopify/shop-minis-platform'
2
2
  import {ShopActions} from '@shopify/shop-minis-platform/actions'
3
3
 
4
4
  // Helper functions for common data structures
5
- const createProduct = (
5
+ export const createProduct = (
6
6
  id: string,
7
7
  title: string,
8
8
  price = '99.99',
@@ -21,7 +21,7 @@ const createProduct = (
21
21
  featuredImage: {url: `https://picsum.photos/400/400`, altText: title},
22
22
  })
23
23
 
24
- const createShop = (id: string, name: string) => ({
24
+ export const createShop = (id: string, name: string) => ({
25
25
  id,
26
26
  name,
27
27
  isFollowing: false,
@@ -29,6 +29,11 @@ const createShop = (id: string, name: string) => ({
29
29
  url: `https://${name.toLowerCase().replace(/\s+/g, '-')}.com`,
30
30
  },
31
31
  reviewAnalytics: {averageRating: 4.3, reviewCount: 50},
32
+ visualTheme: {
33
+ id: 'visual-theme-1',
34
+ featuredImages: [{url: `https://picsum.photos/400/400`, sensitive: false}],
35
+ logoImage: {url: `https://picsum.photos/100/400`, sensitive: false},
36
+ },
32
37
  })
33
38
 
34
39
  const createPagination = (hasNext = false) => ({
@@ -283,14 +288,27 @@ function makeMockActions(): ShopActions {
283
288
  return mock as ShopActions
284
289
  }
285
290
 
291
+ // Detect if running on a mobile device
292
+ const isMobile = (): boolean => {
293
+ const userAgent = navigator.userAgent.toLowerCase()
294
+ const isIOS = /iphone|ipad|ipod/.test(userAgent)
295
+ const isAndroid = /android/.test(userAgent)
296
+
297
+ return isIOS || isAndroid
298
+ }
299
+
286
300
  export const injectMocks = () => {
287
- window.minisSDK = makeMockActions()
288
- window.minisParams = {
289
- handle: 'mock-handle',
290
- initialUrl: '/mock-initial-url',
301
+ // Only inject mocks if we aren't on a mobile device
302
+ if (isMobile()) {
303
+ return
291
304
  }
292
- }
293
305
 
294
- if (!window.minisSDK) {
295
- injectMocks()
306
+ if (!window.minisSDK) {
307
+ window.minisSDK = makeMockActions()
308
+ window.minisParams = {
309
+ handle: 'mock-handle',
310
+ initialUrl: '/mock-initial-url',
311
+ platform: 'web',
312
+ }
313
+ }
296
314
  }
@@ -0,0 +1,124 @@
1
+ import {
2
+ Accordion,
3
+ AccordionContent,
4
+ AccordionItem,
5
+ AccordionTrigger,
6
+ } from '../components/ui/accordion'
7
+
8
+ import type {Meta, StoryObj} from '@storybook/react-vite'
9
+
10
+ const meta = {
11
+ title: 'UI/Accordion',
12
+ component: Accordion,
13
+ parameters: {},
14
+ args: {
15
+ type: 'single',
16
+ collapsible: false,
17
+ },
18
+ tags: ['autodocs'],
19
+ } satisfies Meta<typeof Accordion>
20
+
21
+ export default meta
22
+ type Story = StoryObj<typeof meta>
23
+
24
+ export const NonCollapsible: Story = {
25
+ args: {
26
+ type: 'single',
27
+ collapsible: false,
28
+ },
29
+ render: args => (
30
+ <Accordion {...args}>
31
+ <AccordionItem value="item-2">
32
+ <AccordionTrigger>Can i collapse this?</AccordionTrigger>
33
+ <AccordionContent>
34
+ No. When you set the collapsible prop to false, the accordion is not
35
+ collapsible.
36
+ </AccordionContent>
37
+ </AccordionItem>
38
+ </Accordion>
39
+ ),
40
+ }
41
+
42
+ export const Collapsible: Story = {
43
+ args: {
44
+ type: 'single',
45
+ collapsible: true,
46
+ },
47
+ render: args => (
48
+ <Accordion {...args}>
49
+ <AccordionItem value="item-2">
50
+ <AccordionTrigger>Can i collapse this?</AccordionTrigger>
51
+ <AccordionContent>
52
+ Yes. When you set the collapsible prop to true, you can collapse the
53
+ </AccordionContent>
54
+ </AccordionItem>
55
+ </Accordion>
56
+ ),
57
+ }
58
+
59
+ export const Single: Story = {
60
+ args: {
61
+ type: 'single',
62
+ },
63
+ render: args => (
64
+ <Accordion {...args}>
65
+ <AccordionItem value="item-1">
66
+ <AccordionTrigger>Single item only</AccordionTrigger>
67
+ <AccordionContent>
68
+ With the single type, only one item can be open at a time.
69
+ </AccordionContent>
70
+ </AccordionItem>
71
+ <AccordionItem value="item-2">
72
+ <AccordionTrigger>Another item</AccordionTrigger>
73
+ <AccordionContent>
74
+ Opening this item will close the previous one.
75
+ </AccordionContent>
76
+ </AccordionItem>
77
+ </Accordion>
78
+ ),
79
+ }
80
+
81
+ export const Multiple: Story = {
82
+ args: {
83
+ type: 'multiple',
84
+ },
85
+ render: args => (
86
+ <Accordion {...args}>
87
+ <AccordionItem value="item-1">
88
+ <AccordionTrigger>Can I open multiple items?</AccordionTrigger>
89
+ <AccordionContent>
90
+ Yes, you can open multiple items at the same time with the multiple
91
+ type.
92
+ </AccordionContent>
93
+ </AccordionItem>
94
+ <AccordionItem value="item-2">
95
+ <AccordionTrigger>Is it customizable?</AccordionTrigger>
96
+ <AccordionContent>
97
+ Yes, you can customize the styling and behavior to match your needs.
98
+ </AccordionContent>
99
+ </AccordionItem>
100
+ <AccordionItem value="item-1">
101
+ <AccordionTrigger>Long content example</AccordionTrigger>
102
+ <AccordionContent>
103
+ <div className="space-y-4">
104
+ <p>
105
+ This is a longer content example that demonstrates how the
106
+ accordion handles substantial amounts of text and content.
107
+ </p>
108
+ <p>
109
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
110
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
111
+ enim ad minim veniam, quis nostrud exercitation ullamco laboris
112
+ nisi ut aliquip ex ea commodo consequat.
113
+ </p>
114
+ <ul className="list-disc list-inside space-y-1">
115
+ <li>First item in the list</li>
116
+ <li>Second item in the list</li>
117
+ <li>Third item in the list</li>
118
+ </ul>
119
+ </div>
120
+ </AccordionContent>
121
+ </AccordionItem>
122
+ </Accordion>
123
+ ),
124
+ }
@@ -0,0 +1,38 @@
1
+ import {Alert, AlertTitle, AlertDescription} from '../components/ui/alert'
2
+
3
+ import type {Meta, StoryObj} from '@storybook/react-vite'
4
+
5
+ const meta = {
6
+ title: 'UI/Alert',
7
+ render: ({variant}) => (
8
+ <Alert variant={variant}>
9
+ <AlertTitle>Alert Title</AlertTitle>
10
+ <AlertDescription>
11
+ This is a default alert with a title and description.
12
+ </AlertDescription>
13
+ </Alert>
14
+ ),
15
+
16
+ parameters: {},
17
+ tags: ['autodocs'],
18
+ argTypes: {
19
+ variant: {
20
+ control: 'radio',
21
+ options: ['default', 'destructive'],
22
+ },
23
+ },
24
+ } satisfies Meta<typeof Alert>
25
+
26
+ export default meta
27
+ type Story = StoryObj<typeof meta>
28
+
29
+ export const Default: Story = {
30
+ args: {
31
+ variant: 'default',
32
+ },
33
+ }
34
+ export const Destructive: Story = {
35
+ args: {
36
+ variant: 'destructive',
37
+ },
38
+ }