Skip to content

Commit 61be702

Browse files
Switch from react-spring to motion
1 parent 2fa9b42 commit 61be702

File tree

15 files changed

+299
-339
lines changed

15 files changed

+299
-339
lines changed

app/components/RoundedSector.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55
*
66
* Copyright Oxide Computer Company
77
*/
8+
import { useReducedMotion } from 'motion/react'
89
import { useEffect, useMemo, useState } from 'react'
910

10-
import { useReducedMotion } from '~/hooks/use-reduce-motion'
11-
1211
export function RoundedSector({
1312
angle,
1413
size,

app/components/ToastStack.tsx

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,38 @@
55
*
66
* Copyright Oxide Computer Company
77
*/
8-
import { animated, useTransition } from '@react-spring/web'
8+
import { AnimatePresence, m } from 'motion/react'
99

1010
import { removeToast, useToastStore } from '~/stores/toast'
1111
import { Toast } from '~/ui/lib/Toast'
1212

1313
export function ToastStack() {
1414
const toasts = useToastStore((state) => state.toasts)
1515

16-
const transition = useTransition(toasts, {
17-
keys: (toast) => toast.id,
18-
from: { opacity: 0, y: 10, scale: 95 },
19-
enter: { opacity: 1, y: 0, scale: 100 },
20-
leave: { opacity: 0, y: 10, scale: 95 },
21-
config: { duration: 100 },
22-
})
23-
2416
return (
2517
<div
2618
className="pointer-events-auto fixed bottom-4 left-4 z-toast flex flex-col items-end space-y-2"
2719
data-testid="Toasts"
2820
>
29-
{transition((style, item) => (
30-
<animated.div
31-
style={{
32-
opacity: style.opacity,
33-
y: style.y,
34-
transform: style.scale.to((val) => `scale(${val}%, ${val}%)`),
35-
}}
36-
>
37-
<Toast
38-
key={item.id}
39-
{...item.options}
40-
onClose={() => {
41-
removeToast(item.id)
42-
item.options.onClose?.()
43-
}}
44-
/>
45-
</animated.div>
46-
))}
21+
<AnimatePresence>
22+
{toasts.map((toast) => (
23+
<m.div
24+
key={toast.id}
25+
initial={{ opacity: 0, y: 20, scale: 0.95 }}
26+
animate={{ opacity: 1, y: 0, scale: 1 }}
27+
exit={{ opacity: 0, y: 20, scale: 0.95 }}
28+
transition={{ type: 'spring', duration: 0.2, bounce: 0 }}
29+
>
30+
<Toast
31+
{...toast.options}
32+
onClose={() => {
33+
removeToast(toast.id)
34+
toast.options.onClose?.()
35+
}}
36+
/>
37+
</m.div>
38+
))}
39+
</AnimatePresence>
4740
</div>
4841
)
4942
}

app/hooks/use-reduce-motion.tsx

Lines changed: 0 additions & 38 deletions
This file was deleted.

app/main.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* Copyright Oxide Computer Company
77
*/
88
import { QueryClientProvider } from '@tanstack/react-query'
9+
import { domAnimation, LazyMotion } from 'motion/react'
910
// import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
1011
import { StrictMode } from 'react'
1112
import { createRoot } from 'react-dom/client'
@@ -16,7 +17,6 @@ import { queryClient } from '@oxide/api'
1617

1718
import { ConfirmActionModal } from './components/ConfirmActionModal'
1819
import { ErrorBoundary } from './components/ErrorBoundary'
19-
import { ReduceMotion } from './hooks/use-reduce-motion'
2020
// stripped out by rollup in production
2121
import { startMockAPI } from './msw-mock-api'
2222
import { routes } from './routes'
@@ -46,12 +46,13 @@ function render() {
4646
root.render(
4747
<StrictMode>
4848
<QueryClientProvider client={queryClient}>
49-
<ErrorBoundary>
50-
<ConfirmActionModal />
51-
<SkipLink id="skip-nav" />
52-
<ReduceMotion />
53-
<RouterProvider router={router} />
54-
</ErrorBoundary>
49+
<LazyMotion strict features={domAnimation}>
50+
<ErrorBoundary>
51+
<ConfirmActionModal />
52+
<SkipLink id="skip-nav" />
53+
<RouterProvider router={router} />
54+
</ErrorBoundary>
55+
</LazyMotion>
5556
{/* <ReactQueryDevtools initialIsOpen={false} /> */}
5657
</QueryClientProvider>
5758
</StrictMode>

app/ui/lib/Button.tsx

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* Copyright Oxide Computer Company
77
*/
88
import cn from 'classnames'
9+
import { m } from 'motion/react'
910
import { forwardRef, type MouseEventHandler, type ReactNode } from 'react'
1011

1112
import { Spinner } from '~/ui/lib/Spinner'
@@ -90,9 +91,14 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
9091
with={<Tooltip content={disabledReason} ref={ref} placement="bottom" />}
9192
>
9293
<button
93-
className={cn(buttonStyle({ size, variant }), className, {
94-
'visually-disabled': isDisabled,
95-
})}
94+
className={cn(
95+
buttonStyle({ size, variant }),
96+
className,
97+
{
98+
'visually-disabled': isDisabled,
99+
},
100+
'overflow-hidden'
101+
)}
96102
ref={ref}
97103
/* eslint-disable-next-line react/button-has-type */
98104
type={type}
@@ -101,10 +107,26 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
101107
aria-disabled={isDisabled}
102108
{...rest}
103109
>
104-
{loading && <Spinner className="absolute" variant={variant} />}
105-
<span className={cn('flex items-center', innerClassName, { invisible: loading })}>
110+
{loading && (
111+
<m.span
112+
animate={{ opacity: 1, y: '-50%', x: '-50%' }}
113+
initial={{ opacity: 0, y: 'calc(-50% - 25px)', x: '-50%' }}
114+
transition={{ type: 'spring', duration: 0.3, bounce: 0 }}
115+
className="absolute left-1/2 top-1/2"
116+
>
117+
<Spinner variant={variant} />
118+
</m.span>
119+
)}
120+
<m.span
121+
className={cn('flex items-center', innerClassName)}
122+
animate={{
123+
opacity: loading ? 0 : 1,
124+
y: loading ? 25 : 0,
125+
}}
126+
transition={{ type: 'spring', duration: 0.3, bounce: 0 }}
127+
>
106128
{children}
107-
</span>
129+
</m.span>
108130
</button>
109131
</Wrap>
110132
)

app/ui/lib/CopyToClipboard.tsx

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
* Copyright Oxide Computer Company
77
*/
88

9-
import { animated, config, useTransition } from '@react-spring/web'
109
import cn from 'classnames'
10+
import { AnimatePresence, m } from 'motion/react'
1111
import { useState } from 'react'
1212

1313
import { Copy12Icon, Success12Icon } from '@oxide/design-system/icons/react'
@@ -20,6 +20,11 @@ type Props = {
2020
className?: string
2121
}
2222

23+
const variants = {
24+
hidden: { opacity: 0, scale: 0.75 },
25+
visible: { opacity: 1, scale: 1 },
26+
}
27+
2328
export const CopyToClipboard = ({
2429
ariaLabel = 'Click to copy',
2530
text,
@@ -35,14 +40,14 @@ export const CopyToClipboard = ({
3540
})
3641
}
3742

38-
const transitions = useTransition(hasCopied, {
39-
from: { opacity: 0, transform: 'scale(0.8)' },
40-
enter: { opacity: 1, transform: 'scale(1)' },
41-
leave: { opacity: 0, transform: 'scale(0.8)' },
42-
config: config.stiff,
43-
trail: 100,
44-
initial: null,
45-
})
43+
const animateProps = {
44+
className: 'absolute inset-0 flex items-center justify-center',
45+
variants,
46+
initial: 'hidden',
47+
animate: 'visible',
48+
exit: 'hidden',
49+
transition: { type: 'spring', duration: 0.2, bounce: 0 },
50+
}
4651

4752
return (
4853
<button
@@ -58,14 +63,17 @@ export const CopyToClipboard = ({
5863
type="button"
5964
aria-label={hasCopied ? 'Copied' : ariaLabel}
6065
>
61-
{transitions((styles, item) => (
62-
<animated.div
63-
style={styles}
64-
className="absolute inset-0 flex items-center justify-center"
65-
>
66-
{item ? <Success12Icon /> : <Copy12Icon />}
67-
</animated.div>
68-
))}
66+
<AnimatePresence mode="wait" initial={false}>
67+
{hasCopied ? (
68+
<m.span key="checkmark" {...animateProps}>
69+
<Success12Icon />
70+
</m.span>
71+
) : (
72+
<m.span key="copy" {...animateProps}>
73+
<Copy12Icon />
74+
</m.span>
75+
)}
76+
</AnimatePresence>
6977
</button>
7078
)
7179
}

app/ui/lib/DialogOverlay.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@
66
* Copyright Oxide Computer Company
77
*/
88

9+
import { m } from 'motion/react'
910
import { forwardRef } from 'react'
1011

1112
export const DialogOverlay = forwardRef<HTMLDivElement>((_, ref) => (
12-
<div
13+
<m.div
1314
ref={ref}
1415
aria-hidden
1516
className="fixed inset-0 z-modalOverlay overflow-auto bg-scrim"
17+
initial={{ opacity: 0 }}
18+
animate={{ opacity: 1 }}
19+
exit={{ opacity: 0 }}
20+
transition={{ duration: 0.15, ease: 'easeOut' }}
1621
/>
1722
))

0 commit comments

Comments
 (0)