@shopify/shop-minis-react 0.1.5 → 0.1.6

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 (60) hide show
  1. package/dist/_virtual/index10.js +2 -2
  2. package/dist/_virtual/index5.js +2 -3
  3. package/dist/_virtual/index5.js.map +1 -1
  4. package/dist/_virtual/index6.js +3 -2
  5. package/dist/_virtual/index6.js.map +1 -1
  6. package/dist/_virtual/index7.js +2 -3
  7. package/dist/_virtual/index7.js.map +1 -1
  8. package/dist/_virtual/index8.js +3 -2
  9. package/dist/_virtual/index8.js.map +1 -1
  10. package/dist/_virtual/index9.js +2 -2
  11. package/dist/components/atoms/list.js +106 -41
  12. package/dist/components/atoms/list.js.map +1 -1
  13. package/dist/components/commerce/add-to-cart.js +82 -0
  14. package/dist/components/commerce/add-to-cart.js.map +1 -0
  15. package/dist/components/{atoms → commerce}/favorite-button.js +1 -1
  16. package/dist/components/commerce/favorite-button.js.map +1 -0
  17. package/dist/components/commerce/product-card.js +10 -10
  18. package/dist/components/commerce/product-card.js.map +1 -1
  19. package/dist/components/commerce/product-link.js +6 -6
  20. package/dist/components/commerce/product-link.js.map +1 -1
  21. package/dist/index.js +276 -274
  22. package/dist/index.js.map +1 -1
  23. package/dist/internal/components/refresh-indicator.js +83 -0
  24. package/dist/internal/components/refresh-indicator.js.map +1 -0
  25. package/dist/internal/usePullToRefresh.js +149 -0
  26. package/dist/internal/usePullToRefresh.js.map +1 -0
  27. package/dist/internal/utils/virtuoso-dom.js +20 -0
  28. package/dist/internal/utils/virtuoso-dom.js.map +1 -0
  29. 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
  30. package/dist/shop-minis-react/node_modules/.pnpm/@videojs_xhr@2.7.0/node_modules/@videojs/xhr/lib/index.js +1 -1
  31. package/dist/shop-minis-react/node_modules/.pnpm/@xmldom_xmldom@0.8.10/node_modules/@xmldom/xmldom/lib/index.js +1 -1
  32. package/dist/shop-minis-react/node_modules/.pnpm/color-string@1.9.1/node_modules/color-string/index.js +1 -1
  33. package/dist/shop-minis-react/node_modules/.pnpm/motion@12.17.3_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/motion/dist/es/framer-motion/dist/es/components/AnimatePresence/PopChild.js +55 -0
  34. package/dist/shop-minis-react/node_modules/.pnpm/motion@12.17.3_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/motion/dist/es/framer-motion/dist/es/components/AnimatePresence/PopChild.js.map +1 -0
  35. package/dist/shop-minis-react/node_modules/.pnpm/motion@12.17.3_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/motion/dist/es/framer-motion/dist/es/components/AnimatePresence/PresenceChild.js +35 -0
  36. package/dist/shop-minis-react/node_modules/.pnpm/motion@12.17.3_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/motion/dist/es/framer-motion/dist/es/components/AnimatePresence/PresenceChild.js.map +1 -0
  37. package/dist/shop-minis-react/node_modules/.pnpm/motion@12.17.3_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/motion/dist/es/framer-motion/dist/es/components/AnimatePresence/index.js +46 -0
  38. package/dist/shop-minis-react/node_modules/.pnpm/motion@12.17.3_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/motion/dist/es/framer-motion/dist/es/components/AnimatePresence/index.js.map +1 -0
  39. package/dist/shop-minis-react/node_modules/.pnpm/motion@12.17.3_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/motion/dist/es/framer-motion/dist/es/components/AnimatePresence/utils.js +13 -0
  40. package/dist/shop-minis-react/node_modules/.pnpm/motion@12.17.3_react-dom@19.1.0_react@19.1.0__react@19.1.0/node_modules/motion/dist/es/framer-motion/dist/es/components/AnimatePresence/utils.js.map +1 -0
  41. package/dist/shop-minis-react/node_modules/.pnpm/mpd-parser@1.3.1/node_modules/mpd-parser/dist/mpd-parser.es.js +1 -1
  42. package/dist/shop-minis-react/node_modules/.pnpm/simple-swizzle@0.2.2/node_modules/simple-swizzle/index.js +1 -1
  43. 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 +1 -1
  44. package/package.json +1 -1
  45. package/src/components/atoms/list.tsx +97 -12
  46. package/src/components/commerce/add-to-cart.test.tsx +73 -0
  47. package/src/components/commerce/add-to-cart.tsx +132 -0
  48. package/src/components/{atoms → commerce}/favorite-button.tsx +1 -1
  49. package/src/components/commerce/product-card.tsx +2 -1
  50. package/src/components/commerce/product-link.tsx +2 -1
  51. package/src/components/index.ts +2 -1
  52. package/src/internal/components/refresh-indicator.tsx +103 -0
  53. package/src/internal/usePullToRefresh.ts +286 -0
  54. package/src/internal/utils/virtuoso-dom.ts +26 -0
  55. package/src/stories/AddToCart.stories.tsx +186 -0
  56. package/src/stories/FavoriteButton.stories.tsx +2 -2
  57. package/src/stories/PullToRefreshList.stories.tsx +122 -0
  58. package/src/styles/animations.css +54 -0
  59. package/dist/components/atoms/favorite-button.js.map +0 -1
  60. /package/src/components/{atoms → commerce}/favorite-button.test.tsx +0 -0
@@ -1,12 +1,18 @@
1
- import {useCallback, useRef} from 'react'
1
+ import {useCallback, useEffect, useRef} from 'react'
2
2
 
3
3
  import {Virtuoso, VirtuosoProps} from 'react-virtuoso'
4
4
 
5
+ import {RefreshIndicator} from '../../internal/components/refresh-indicator'
6
+ import {usePullToRefresh} from '../../internal/usePullToRefresh'
7
+ import {findVirtuosoScrollableElement} from '../../internal/utils/virtuoso-dom'
5
8
  import {cn} from '../../lib/utils'
6
9
  import '../../styles/utilities.css'
7
10
 
8
11
  import {Pagination} from './pagination'
9
12
 
13
+ const DEFAULT_REFRESH_PULL_THRESHOLD = 200
14
+ const ELEMENT_BIND_DELAY = 100
15
+
10
16
  interface Props<T = any>
11
17
  extends Omit<
12
18
  VirtuosoProps<T, unknown>,
@@ -19,6 +25,9 @@ interface Props<T = any>
19
25
  fetchMore?: () => Promise<void>
20
26
  loadingComponent?: React.ReactNode
21
27
  isFetchingMore?: boolean
28
+ onRefresh?: () => Promise<void>
29
+ refreshing?: boolean
30
+ enablePullToRefresh?: boolean
22
31
  }
23
32
 
24
33
  export function List<T = any>({
@@ -31,9 +40,20 @@ export function List<T = any>({
31
40
  fetchMore,
32
41
  loadingComponent,
33
42
  isFetchingMore,
43
+ onRefresh,
44
+ refreshing,
45
+ enablePullToRefresh = true,
34
46
  ...virtuosoProps
35
47
  }: Props<T>) {
36
48
  const inFlightFetchMoreRef = useRef<Promise<void> | null>(null)
49
+ const virtuosoRef = useRef<any>(null)
50
+ const containerRef = useRef<HTMLDivElement>(null)
51
+
52
+ const {state: pullToRefreshState, bindToElement} = usePullToRefresh({
53
+ onRefresh,
54
+ threshold: DEFAULT_REFRESH_PULL_THRESHOLD,
55
+ enabled: enablePullToRefresh && Boolean(onRefresh),
56
+ })
37
57
 
38
58
  const _fetchMore = useCallback(() => {
39
59
  // Dedupe concurrent calls by returning the same in-flight promise
@@ -68,18 +88,83 @@ export function List<T = any>({
68
88
 
69
89
  const classNames = cn(showScrollbar ? undefined : 'no-scrollbars', className)
70
90
 
91
+ useEffect(() => {
92
+ if (containerRef.current && enablePullToRefresh && onRefresh) {
93
+ let cleanup: (() => void) | undefined
94
+
95
+ const findAndBind = () => {
96
+ if (!containerRef.current) return
97
+
98
+ const scrollableElement = findVirtuosoScrollableElement(
99
+ containerRef.current
100
+ )
101
+ cleanup = bindToElement(scrollableElement)
102
+ }
103
+
104
+ const timeoutId = setTimeout(findAndBind, ELEMENT_BIND_DELAY)
105
+
106
+ return () => {
107
+ clearTimeout(timeoutId)
108
+ if (cleanup) cleanup()
109
+ }
110
+ }
111
+ return undefined
112
+ }, [bindToElement, enablePullToRefresh, onRefresh])
113
+
114
+ const EnhancedHeader = useCallback(() => {
115
+ const effectivePullDistance = refreshing
116
+ ? Math.max(pullToRefreshState.pullDistance, 140)
117
+ : pullToRefreshState.pullDistance
118
+
119
+ const refreshHeaderHeight = Math.min(
120
+ Math.max(effectivePullDistance, 0),
121
+ 140
122
+ )
123
+
124
+ return (
125
+ <>
126
+ {enablePullToRefresh && onRefresh && (
127
+ <div
128
+ className="flex items-center justify-center"
129
+ style={{
130
+ height: refreshHeaderHeight,
131
+ overflow: 'hidden',
132
+ }}
133
+ >
134
+ <RefreshIndicator
135
+ pullDistance={pullToRefreshState.pullDistance}
136
+ threshold={DEFAULT_REFRESH_PULL_THRESHOLD}
137
+ isRefreshing={refreshing ?? false}
138
+ canRefresh={pullToRefreshState.canRefresh}
139
+ className="relative top-0 inset-x-auto"
140
+ />
141
+ </div>
142
+ )}
143
+ {header && <div>{header}</div>}
144
+ </>
145
+ )
146
+ }, [header, enablePullToRefresh, onRefresh, pullToRefreshState, refreshing])
147
+
71
148
  return (
72
- <Virtuoso
73
- className={classNames}
74
- style={{height}}
75
- data={items}
76
- itemContent={itemContent}
77
- components={{
78
- Header: header ? () => <div>{header}</div> : undefined,
79
- Footer,
149
+ <div
150
+ ref={containerRef}
151
+ className={cn('relative transition-all duration-200', classNames)}
152
+ style={{
153
+ height,
80
154
  }}
81
- endReached={fetchMore ? _fetchMore : undefined}
82
- {...virtuosoProps}
83
- />
155
+ >
156
+ <Virtuoso
157
+ ref={virtuosoRef}
158
+ className="h-full w-full"
159
+ data={items}
160
+ itemContent={itemContent}
161
+ components={{
162
+ Header: EnhancedHeader,
163
+ Footer,
164
+ }}
165
+ endReached={fetchMore ? _fetchMore : undefined}
166
+ {...virtuosoProps}
167
+ />
168
+ </div>
84
169
  )
85
170
  }
@@ -0,0 +1,73 @@
1
+ import {describe, expect, it, vi} from 'vitest'
2
+
3
+ import {render, screen, mockMinisSDK, resetAllMocks} from '../../test-utils'
4
+
5
+ import {AddToCartButton} from './add-to-cart'
6
+
7
+ // Mock hooks
8
+ vi.mock('../../hooks/shop/useShopCartActions', () => ({
9
+ useShopCartActions: () => ({
10
+ addToCart: mockMinisSDK.addToCart,
11
+ buyProduct: mockMinisSDK.buyProduct,
12
+ }),
13
+ }))
14
+
15
+ describe('AddToCartButton', () => {
16
+ const defaultProps = {
17
+ productId: 'gid://shopify/Product/123',
18
+ productVariantId: 'gid://shopify/ProductVariant/456',
19
+ }
20
+
21
+ // eslint-disable-next-line jest/require-top-level-describe
22
+ beforeEach(() => {
23
+ resetAllMocks()
24
+ })
25
+
26
+ it('renders with default text', () => {
27
+ render(<AddToCartButton {...defaultProps} />)
28
+
29
+ expect(screen.getByRole('button')).toBeInTheDocument()
30
+ expect(screen.getByText('Add to cart')).toBeInTheDocument()
31
+ })
32
+
33
+ it('renders with required props', () => {
34
+ render(<AddToCartButton {...defaultProps} />)
35
+
36
+ const button = screen.getByRole('button')
37
+ expect(button).toBeInTheDocument()
38
+ })
39
+
40
+ it('respects disabled prop', () => {
41
+ render(<AddToCartButton {...defaultProps} disabled />)
42
+
43
+ const button = screen.getByRole('button')
44
+ expect(button).toBeDisabled()
45
+ })
46
+
47
+ it('applies custom className', () => {
48
+ render(<AddToCartButton {...defaultProps} className="custom-class" />)
49
+
50
+ const button = screen.getByRole('button')
51
+ expect(button).toHaveClass('custom-class')
52
+ })
53
+
54
+ it('renders with different sizes', () => {
55
+ const {rerender} = render(<AddToCartButton {...defaultProps} size="sm" />)
56
+
57
+ expect(screen.getByRole('button')).toBeInTheDocument()
58
+
59
+ rerender(<AddToCartButton {...defaultProps} size="default" />)
60
+ expect(screen.getByRole('button')).toBeInTheDocument()
61
+
62
+ rerender(<AddToCartButton {...defaultProps} size="lg" />)
63
+ expect(screen.getByRole('button')).toBeInTheDocument()
64
+ })
65
+
66
+ it('renders with discount codes prop', () => {
67
+ const discountCodes = ['SUMMER20', 'FREESHIP']
68
+
69
+ render(<AddToCartButton {...defaultProps} discountCodes={discountCodes} />)
70
+
71
+ expect(screen.getByRole('button')).toBeInTheDocument()
72
+ })
73
+ })
@@ -0,0 +1,132 @@
1
+ import * as React from 'react'
2
+ import {useState, useCallback} from 'react'
3
+
4
+ import {CheckIcon} from 'lucide-react'
5
+ import {motion, AnimatePresence} from 'motion/react'
6
+
7
+ import {useErrorToast, useShopCartActions} from '../../hooks'
8
+ import {cn} from '../../lib/utils'
9
+ import {Button} from '../atoms/button'
10
+
11
+ interface AddToCartButtonProps {
12
+ disabled?: boolean
13
+ className?: string
14
+ size?: 'default' | 'sm' | 'lg'
15
+ /**
16
+ * The discount codes to apply to the cart.
17
+ */
18
+ discountCodes?: string[]
19
+ /**
20
+ * The GID of the product. E.g. `gid://shopify/Product/123`.
21
+ */
22
+ productId: string
23
+ /**
24
+ * The GID of the product variant. E.g. `gid://shopify/ProductVariant/456`.
25
+ */
26
+ productVariantId: string
27
+ }
28
+
29
+ export function AddToCartButton({
30
+ disabled = false,
31
+ className,
32
+ size = 'default',
33
+ productId,
34
+ productVariantId,
35
+ discountCodes,
36
+ }: AddToCartButtonProps) {
37
+ const {addToCart} = useShopCartActions()
38
+ const [isAdded, setIsAdded] = useState(false)
39
+ const timeoutRef = React.useRef<number | undefined>(undefined)
40
+
41
+ const {showErrorToast} = useErrorToast()
42
+
43
+ const handleClick = useCallback(async () => {
44
+ if (isAdded || disabled) return
45
+
46
+ try {
47
+ // Call the callback if provided
48
+ if (productId && productVariantId) {
49
+ // Optimistic update with error toast
50
+ addToCart({
51
+ productId,
52
+ productVariantId,
53
+ quantity: 1,
54
+ discountCodes,
55
+ })
56
+ .then(() => {})
57
+ .catch(() => {
58
+ showErrorToast({message: 'Failed to add to cart'})
59
+ })
60
+ }
61
+
62
+ // Show success state
63
+ setIsAdded(true)
64
+
65
+ // Clear any existing timeout
66
+ if (timeoutRef.current) {
67
+ clearTimeout(timeoutRef.current)
68
+ }
69
+
70
+ // Reset to initial state after delay
71
+ timeoutRef.current = window.setTimeout(() => {
72
+ setIsAdded(false)
73
+ }, 2000)
74
+ } catch (error) {
75
+ // Handle error - reset to initial state
76
+ setIsAdded(false)
77
+ console.error('Failed to add to cart:', error)
78
+ }
79
+ }, [
80
+ isAdded,
81
+ disabled,
82
+ addToCart,
83
+ productId,
84
+ productVariantId,
85
+ discountCodes,
86
+ showErrorToast,
87
+ ])
88
+
89
+ // Cleanup timeout on unmount
90
+ React.useEffect(() => {
91
+ return () => {
92
+ if (timeoutRef.current) {
93
+ clearTimeout(timeoutRef.current)
94
+ }
95
+ }
96
+ }, [])
97
+
98
+ return (
99
+ <Button
100
+ onClick={handleClick}
101
+ disabled={disabled}
102
+ className={cn(
103
+ 'relative overflow-hidden transition-all duration-300',
104
+ className
105
+ )}
106
+ size={size}
107
+ >
108
+ <div className="relative flex items-center justify-center">
109
+ <AnimatePresence>
110
+ {isAdded && (
111
+ <motion.div
112
+ initial={{scale: 0, rotate: -180}}
113
+ animate={{scale: 1, rotate: 0}}
114
+ exit={{scale: 0, rotate: 180}}
115
+ transition={{
116
+ duration: 0.4,
117
+ ease: [0.175, 0.885, 0.32, 1.275], // bounce effect
118
+ }}
119
+ className="absolute left-0"
120
+ style={{x: -8}}
121
+ >
122
+ <CheckIcon className="size-4" />
123
+ </motion.div>
124
+ )}
125
+ </AnimatePresence>
126
+ <span className={cn(isAdded && 'pl-5', 'transition-all duration-300')}>
127
+ {isAdded ? 'Added to cart' : 'Add to cart'}
128
+ </span>
129
+ </div>
130
+ </Button>
131
+ )
132
+ }
@@ -1,6 +1,6 @@
1
1
  import {Heart} from 'lucide-react'
2
2
 
3
- import {IconButton} from './icon-button'
3
+ import {IconButton} from '../atoms/icon-button'
4
4
 
5
5
  export function FavoriteButton({
6
6
  onClick,
@@ -7,12 +7,13 @@ import {useShopNavigation} from '../../hooks/navigation/useShopNavigation'
7
7
  import {useSavedProductsActions} from '../../hooks/user/useSavedProductsActions'
8
8
  import {formatMoney} from '../../lib/formatMoney'
9
9
  import {cn} from '../../lib/utils'
10
- import {FavoriteButton} from '../atoms/favorite-button'
11
10
  import {Image} from '../atoms/image'
12
11
  import {ProductVariantPrice} from '../atoms/product-variant-price'
13
12
  import {Touchable} from '../atoms/touchable'
14
13
  import {Badge} from '../ui/badge'
15
14
 
15
+ import {FavoriteButton} from './favorite-button'
16
+
16
17
  // Context definition
17
18
  interface ProductCardContextValue {
18
19
  // Core data
@@ -9,10 +9,11 @@ import {useShopNavigation} from '../../hooks/navigation/useShopNavigation'
9
9
  import {useSavedProductsActions} from '../../hooks/user/useSavedProductsActions'
10
10
  import {formatMoney} from '../../lib/formatMoney'
11
11
  import {cn} from '../../lib/utils'
12
- import {FavoriteButton} from '../atoms/favorite-button'
13
12
  import {Touchable} from '../atoms/touchable'
14
13
  import {Card, CardContent, CardAction} from '../ui/card'
15
14
 
15
+ import {FavoriteButton} from './favorite-button'
16
+
16
17
  const productLinkVariants = cva('', {
17
18
  variants: {
18
19
  layout: {
@@ -1,5 +1,6 @@
1
1
  export * from './MinisContainer'
2
2
 
3
+ export * from './commerce/add-to-cart'
3
4
  export * from './commerce/product-card'
4
5
  export * from './commerce/product-link'
5
6
  export * from './commerce/merchant-card'
@@ -7,6 +8,7 @@ export * from './commerce/product-card-skeleton'
7
8
  export * from './commerce/merchant-card-skeleton'
8
9
  export * from './commerce/quantity-selector'
9
10
  export * from './commerce/search'
11
+ export * from './commerce/favorite-button'
10
12
 
11
13
  export * from './content/image-content-wrapper'
12
14
 
@@ -14,7 +16,6 @@ export * from './navigation/minis-router'
14
16
  export * from './navigation/transition-link'
15
17
 
16
18
  export * from './atoms/button'
17
- export * from './atoms/favorite-button'
18
19
  export * from './atoms/icon-button'
19
20
  export * from './atoms/image'
20
21
  export * from './atoms/touchable'
@@ -0,0 +1,103 @@
1
+ import {forwardRef, useEffect, useState} from 'react'
2
+
3
+ import {cn} from '../../lib/utils'
4
+
5
+ export interface PullToRefreshIndicatorProps {
6
+ pullDistance: number
7
+ threshold: number
8
+ isRefreshing: boolean
9
+ canRefresh: boolean
10
+ className?: string
11
+ }
12
+
13
+ export const RefreshIndicator = forwardRef<
14
+ HTMLDivElement,
15
+ PullToRefreshIndicatorProps
16
+ >(({pullDistance, threshold, isRefreshing, canRefresh, className}, ref) => {
17
+ const [showBumpAnimation, setShowBumpAnimation] = useState(false)
18
+
19
+ const progress = Math.min(pullDistance / threshold, 1)
20
+
21
+ const spinnerProgress = 0.54 + progress * (1 - 0.54)
22
+
23
+ const scale = isRefreshing ? 1 : 0.5 + progress * 0.5
24
+
25
+ const opacity = isRefreshing ? 1 : Math.min(progress * 1.5, 1)
26
+
27
+ const translateY = isRefreshing ? 0 : progress * 3
28
+
29
+ useEffect(() => {
30
+ if (isRefreshing && !showBumpAnimation) {
31
+ setShowBumpAnimation(true)
32
+ const timer = setTimeout(() => setShowBumpAnimation(false), 360)
33
+ return () => clearTimeout(timer)
34
+ }
35
+ return undefined
36
+ }, [isRefreshing, showBumpAnimation])
37
+
38
+ return (
39
+ <div
40
+ ref={ref}
41
+ className={cn(
42
+ 'flex items-center justify-center w-full h-full',
43
+ 'transition-all duration-200 ease-out',
44
+ className
45
+ )}
46
+ style={{
47
+ transform: `translateY(${translateY}px)`,
48
+ }}
49
+ >
50
+ <div
51
+ className={cn(
52
+ 'flex flex-col items-center space-y-2 rounded-full px-4 py-2 backdrop-blur-sm transition-all duration-200',
53
+ canRefresh || isRefreshing
54
+ ? 'bg-primary/20 border-2 border-primary/40'
55
+ : 'bg-background/90'
56
+ )}
57
+ style={{
58
+ opacity,
59
+ transform: `scale(${scale})`,
60
+ }}
61
+ >
62
+ <div
63
+ className={cn(
64
+ 'h-8 w-8 transition-all duration-200',
65
+ showBumpAnimation && 'animate-bump'
66
+ )}
67
+ >
68
+ <svg
69
+ viewBox="0 0 52 58"
70
+ fill="none"
71
+ xmlns="http://www.w3.org/2000/svg"
72
+ className={cn(
73
+ 'h-full w-full transition-colors duration-200',
74
+ canRefresh || isRefreshing
75
+ ? 'text-primary'
76
+ : 'text-muted-foreground'
77
+ )}
78
+ >
79
+ <path
80
+ className={cn(
81
+ 'shop-spinner-path',
82
+ !isRefreshing && 'shop-spinner-progress',
83
+ isRefreshing && 'animate-shop-spin'
84
+ )}
85
+ d="M3 13C5 11.75 10.4968 6.92307 21.5 6.4999C34.5 5.99993 42 13 45 23C48.3 34 42.9211 48.1335 30.5 51C17.5 54 6.6 46 6 37C5.46667 29 10.5 25 14 23"
86
+ stroke="currentColor"
87
+ strokeWidth="8"
88
+ strokeLinecap="square"
89
+ strokeLinejoin="miter"
90
+ style={
91
+ {
92
+ '--spinner-progress': isRefreshing ? '1' : spinnerProgress,
93
+ } as React.CSSProperties
94
+ }
95
+ />
96
+ </svg>
97
+ </div>
98
+ </div>
99
+ </div>
100
+ )
101
+ })
102
+
103
+ RefreshIndicator.displayName = 'RefreshIndicator'