@shopify/shop-minis-react 0.0.17 → 0.0.19
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/index3.js +6 -0
- package/dist/_virtual/index3.js.map +1 -0
- package/dist/_virtual/index4.js +5 -0
- package/dist/_virtual/index4.js.map +1 -0
- package/dist/_virtual/use-sync-external-store-shim.development.js +5 -0
- package/dist/_virtual/use-sync-external-store-shim.development.js.map +1 -0
- package/dist/_virtual/use-sync-external-store-shim.production.js +5 -0
- package/dist/_virtual/use-sync-external-store-shim.production.js.map +1 -0
- package/dist/components/atoms/alert-dialog.js +41 -0
- package/dist/components/atoms/alert-dialog.js.map +1 -0
- package/dist/components/atoms/thumbhash-image.js +54 -0
- package/dist/components/atoms/thumbhash-image.js.map +1 -0
- package/dist/components/commerce/merchant-card-skeleton.js +29 -0
- package/dist/components/commerce/merchant-card-skeleton.js.map +1 -0
- package/dist/components/commerce/merchant-card.js +28 -22
- package/dist/components/commerce/merchant-card.js.map +1 -1
- package/dist/components/commerce/product-card-skeleton.js +20 -0
- package/dist/components/commerce/product-card-skeleton.js.map +1 -0
- package/dist/components/commerce/product-card.js +105 -78
- package/dist/components/commerce/product-card.js.map +1 -1
- package/dist/components/navigation/transition-container.js +8 -0
- package/dist/components/navigation/transition-container.js.map +1 -0
- package/dist/components/navigation/transition-link.js +27 -0
- package/dist/components/navigation/transition-link.js.map +1 -0
- package/dist/components/ui/avatar.js +54 -0
- package/dist/components/ui/avatar.js.map +1 -0
- package/dist/components/ui/skeleton.js +16 -0
- package/dist/components/ui/skeleton.js.map +1 -0
- package/dist/hooks/navigation/useNavigateWithTransition.js +43 -0
- package/dist/hooks/navigation/useNavigateWithTransition.js.map +1 -0
- package/dist/hooks/navigation/useViewTransitions.js +45 -0
- package/dist/hooks/navigation/useViewTransitions.js.map +1 -0
- package/dist/index.js +215 -192
- package/dist/index.js.map +1 -1
- package/dist/shop-minis-react/node_modules/.pnpm/@radix-ui_react-avatar@1.1.10_@types_react-dom@19.1.6_@types_react@19.1.6__@types_react@19.1._hkz57sehyui4ndfh3rsqwxftli/node_modules/@radix-ui/react-avatar/dist/index.js +77 -0
- package/dist/shop-minis-react/node_modules/.pnpm/@radix-ui_react-avatar@1.1.10_@types_react-dom@19.1.6_@types_react@19.1.6__@types_react@19.1._hkz57sehyui4ndfh3rsqwxftli/node_modules/@radix-ui/react-avatar/dist/index.js.map +1 -0
- 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 +16 -0
- 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.map +1 -0
- package/dist/shop-minis-react/node_modules/.pnpm/js-base64@3.7.7/node_modules/js-base64/base64.js +21 -0
- package/dist/shop-minis-react/node_modules/.pnpm/js-base64@3.7.7/node_modules/js-base64/base64.js.map +1 -0
- 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
- 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
- package/dist/shop-minis-react/node_modules/.pnpm/thumbhash@0.1.1/node_modules/thumbhash/thumbhash.js +145 -0
- package/dist/shop-minis-react/node_modules/.pnpm/thumbhash@0.1.1/node_modules/thumbhash/thumbhash.js.map +1 -0
- package/dist/shop-minis-react/node_modules/.pnpm/use-sync-external-store@1.5.0_react@19.1.0/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.development.js +68 -0
- package/dist/shop-minis-react/node_modules/.pnpm/use-sync-external-store@1.5.0_react@19.1.0/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.development.js.map +1 -0
- package/dist/shop-minis-react/node_modules/.pnpm/use-sync-external-store@1.5.0_react@19.1.0/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.production.js +56 -0
- package/dist/shop-minis-react/node_modules/.pnpm/use-sync-external-store@1.5.0_react@19.1.0/node_modules/use-sync-external-store/cjs/use-sync-external-store-shim.production.js.map +1 -0
- package/dist/shop-minis-react/node_modules/.pnpm/use-sync-external-store@1.5.0_react@19.1.0/node_modules/use-sync-external-store/shim/index.js +11 -0
- package/dist/shop-minis-react/node_modules/.pnpm/use-sync-external-store@1.5.0_react@19.1.0/node_modules/use-sync-external-store/shim/index.js.map +1 -0
- package/dist/types/index.js +10 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/image.js +15 -0
- package/dist/utils/image.js.map +1 -0
- package/package.json +13 -3
- package/src/components/atoms/alert-dialog.tsx +67 -0
- package/src/components/atoms/thumbhash-image.tsx +66 -0
- package/src/components/commerce/merchant-card-skeleton.tsx +31 -0
- package/src/components/commerce/merchant-card.tsx +5 -2
- package/src/components/commerce/product-card-skeleton.tsx +30 -0
- package/src/components/commerce/product-card.tsx +49 -8
- package/src/components/index.ts +9 -0
- package/src/components/navigation/transition-container.tsx +7 -0
- package/src/components/navigation/transition-link.tsx +48 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/navigation/useNavigateWithTransition.ts +62 -0
- package/src/hooks/navigation/useViewTransitions.ts +79 -0
- package/src/index.css +1 -0
- package/src/mocks.ts +8 -2
- package/src/stories/Accordion.stories.tsx +124 -0
- package/src/stories/Alert.stories.tsx +38 -0
- package/src/stories/AlertDialog.stories.tsx +48 -0
- package/src/stories/Avatar.stories.tsx +29 -0
- package/src/stories/Badge.stories.tsx +46 -0
- package/src/stories/Button.stories.tsx +81 -0
- package/src/stories/Card.stories.tsx +40 -0
- package/src/stories/Checkbox.stories.tsx +44 -0
- package/src/stories/FavoriteButton.stories.tsx +58 -0
- package/src/stories/IconButton.stories.tsx +68 -0
- package/src/stories/Input.stories.tsx +44 -0
- package/src/stories/Label.stories.tsx +19 -0
- package/src/stories/MerchantCard.stories.tsx +55 -0
- package/src/stories/ProductCard.stories.tsx +85 -0
- package/src/stories/ProductLink.stories.tsx +46 -0
- package/src/stories/Progress.stories.tsx +30 -0
- package/src/stories/RadioGroup.stories.tsx +51 -0
- package/src/stories/Select.stories.tsx +85 -0
- package/src/stories/Skeleton.stories.tsx +19 -0
- package/src/stories/Toaster.stories.tsx +46 -0
- package/src/stories/Touchable.stories.tsx +40 -0
- package/src/styles/animations.css +90 -0
- package/src/styles/globals.css +8 -0
- package/src/styles/theme.css +1 -1
- package/src/types/index.ts +7 -1
- 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
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
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
|
|
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 && (
|
package/src/components/index.ts
CHANGED
|
@@ -3,15 +3,23 @@ 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'
|
|
11
18
|
|
|
12
19
|
export * from './ui/accordion'
|
|
13
20
|
export * from './ui/alert'
|
|
14
21
|
export * from './ui/alert-dialog'
|
|
22
|
+
export * from './ui/avatar'
|
|
15
23
|
export * from './ui/card'
|
|
16
24
|
export * from './ui/carousel'
|
|
17
25
|
export * from './ui/checkbox'
|
|
@@ -27,3 +35,4 @@ export * from './ui/select'
|
|
|
27
35
|
export * from './ui/separator'
|
|
28
36
|
export * from './ui/sheet'
|
|
29
37
|
export * from './ui/sonner'
|
|
38
|
+
export * from './ui/skeleton'
|
|
@@ -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}
|
package/src/hooks/index.ts
CHANGED
|
@@ -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
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) => ({
|
|
@@ -288,6 +293,7 @@ export const injectMocks = () => {
|
|
|
288
293
|
window.minisParams = {
|
|
289
294
|
handle: 'mock-handle',
|
|
290
295
|
initialUrl: '/mock-initial-url',
|
|
296
|
+
platform: 'ios',
|
|
291
297
|
}
|
|
292
298
|
}
|
|
293
299
|
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {fn} from 'storybook/test'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
AlertDialog,
|
|
5
|
+
AlertDialogAction,
|
|
6
|
+
AlertDialogCancel,
|
|
7
|
+
AlertDialogContent,
|
|
8
|
+
AlertDialogDescription,
|
|
9
|
+
AlertDialogFooter,
|
|
10
|
+
AlertDialogHeader,
|
|
11
|
+
AlertDialogTitle,
|
|
12
|
+
AlertDialogTrigger,
|
|
13
|
+
Button,
|
|
14
|
+
} from '../components'
|
|
15
|
+
|
|
16
|
+
import type {Meta, StoryObj} from '@storybook/react-vite'
|
|
17
|
+
|
|
18
|
+
const meta = {
|
|
19
|
+
title: 'Ui/AlertDialog',
|
|
20
|
+
render: () => (
|
|
21
|
+
<AlertDialog>
|
|
22
|
+
<AlertDialogTrigger asChild>
|
|
23
|
+
<Button variant="outline">Open Alert Dialog</Button>
|
|
24
|
+
</AlertDialogTrigger>
|
|
25
|
+
<AlertDialogContent>
|
|
26
|
+
<AlertDialogHeader>
|
|
27
|
+
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
|
|
28
|
+
<AlertDialogDescription>
|
|
29
|
+
This action cannot be undone.
|
|
30
|
+
</AlertDialogDescription>
|
|
31
|
+
</AlertDialogHeader>
|
|
32
|
+
<AlertDialogFooter>
|
|
33
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
34
|
+
<AlertDialogAction onClick={() => fn()}>Continue</AlertDialogAction>
|
|
35
|
+
</AlertDialogFooter>
|
|
36
|
+
</AlertDialogContent>
|
|
37
|
+
</AlertDialog>
|
|
38
|
+
),
|
|
39
|
+
parameters: {},
|
|
40
|
+
tags: ['autodocs'],
|
|
41
|
+
argTypes: {},
|
|
42
|
+
args: {},
|
|
43
|
+
} satisfies Meta<typeof AlertDialog>
|
|
44
|
+
|
|
45
|
+
export default meta
|
|
46
|
+
type Story = StoryObj<typeof meta>
|
|
47
|
+
|
|
48
|
+
export const Default: Story = {}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {Avatar, AvatarFallback, AvatarImage} from '../components/ui/avatar'
|
|
2
|
+
|
|
3
|
+
import type {StoryObj} from '@storybook/react-vite'
|
|
4
|
+
|
|
5
|
+
interface AvatarProps {
|
|
6
|
+
src: 'https://github.com/shadcn.png'
|
|
7
|
+
userInitials: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const meta = {
|
|
11
|
+
title: 'UI/Avatar',
|
|
12
|
+
render: ({userInitials}: AvatarProps) => (
|
|
13
|
+
<Avatar>
|
|
14
|
+
<AvatarImage src="https://github.com/shadcn.png" />
|
|
15
|
+
<AvatarFallback>{userInitials}</AvatarFallback>
|
|
16
|
+
</Avatar>
|
|
17
|
+
),
|
|
18
|
+
args: {
|
|
19
|
+
className: 'size-12',
|
|
20
|
+
src: 'https://github.com/shadcn.png',
|
|
21
|
+
userInitials: 'CN',
|
|
22
|
+
},
|
|
23
|
+
tags: ['autodocs'],
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default meta
|
|
27
|
+
type Story = StoryObj<typeof meta>
|
|
28
|
+
|
|
29
|
+
export const Default: Story = {}
|