-
Notifications
You must be signed in to change notification settings - Fork 585
/
Copy pathprogress-button.tsx
87 lines (80 loc) · 2.87 KB
/
progress-button.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import {VariantProps} from 'class-variance-authority'
import {HTMLMotionProps, motion} from 'framer-motion'
import {CSSProperties, useEffect, useState} from 'react'
import {useFirstMountState} from 'react-use'
import {arrayIncludes} from 'ts-extras'
import {buttonVariants} from '@/shadcn-components/ui/button'
import {cn} from '@/shadcn-lib/utils'
import {AppStateOrLoading, progressBarStates} from '@/trpc/trpc'
// Check if CSS available
// https://developer.mozilla.org/en-US/docs/Web/API/CSS/registerProperty
if (typeof CSS !== 'undefined' && CSS.registerProperty) {
CSS.registerProperty({
name: '--progress-button-progress',
syntax: '<percentage>',
inherits: false,
initialValue: '0%',
})
}
type Props = {
progress?: number
state: AppStateOrLoading
onClick?: () => void
} & VariantProps<typeof buttonVariants> &
HTMLMotionProps<'button'>
export function ProgressButton({variant, size, progress, state, children, className, style, ...buttonProps}: Props) {
const isFirstRender = useFirstMountState()
const progressing = arrayIncludes(progressBarStates, state)
// Stops flicker when progressing done
const [progressingDone, setProgressingDone] = useState(true)
useEffect(() => {
if (state === 'ready') {
setTimeout(() => setProgressingDone(true), 0)
} else if (progressing) {
setProgressingDone(false)
}
}, [state, progressing])
const progressingStyle: CSSProperties = {
// Adding transitions so hover and other transitions work
transition: '--progress-button-progress 0.3s',
// ['--progress-button-bg' as string]: 'var(--color-brand)',
['--progress-button-progress' as string]: `${Math.round(progress ?? 0)}%`,
backgroundImage:
'linear-gradient(to right, var(--progress-button-bg) var(--progress-button-progress), transparent var(--progress-button-progress))',
}
return (
<motion.button
data-progressing={progressing}
className={cn(
buttonVariants({size, variant}),
'select-none whitespace-nowrap disabled:bg-opacity-60 disabled:opacity-100',
state === 'loading' && '!bg-white/10',
// Disable transition right when installing done for a sec to prevent flicker
state === 'ready' && !progressingDone && 'transition-none',
className,
)}
style={{
...(progressing ? progressingStyle : undefined),
...style,
}}
layout
disabled={!arrayIncludes(['not-installed', 'ready'], state)}
{...buttonProps}
>
{/* Child has `layout` too to prevent content from being scaled and stretched with the parent */}
{/* https://codesandbox.io/p/sandbox/framer-motion-2-scale-correction-z4tgr?file=%2Fsrc%2FApp.js&from-embed= */}
<motion.div
layout='position'
key={state}
initial={{opacity: 0}}
animate={{
opacity: 1,
transition: {opacity: {duration: 0.2, delay: state === 'loading' || isFirstRender ? 0 : 0.2}},
}}
// className='bg-red-500/50'
>
{children}
</motion.div>
</motion.button>
)
}