feat: animations for menu, modal, tabs, tooltip
This commit is contained in:
parent
bcd1df1b0e
commit
41c7d8028b
|
|
@ -80,6 +80,7 @@
|
|||
"emoji-mart": "5.5.2",
|
||||
"fast-average-color": "9.4.0",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"framer-motion": "11.0.3",
|
||||
"gpt-tokenizer": "2.1.2",
|
||||
"hast-util-from-html": "2.0.1",
|
||||
"hast-util-to-html": "9.0.0",
|
||||
|
|
|
|||
|
|
@ -159,6 +159,9 @@ dependencies:
|
|||
fast-deep-equal:
|
||||
specifier: 3.1.3
|
||||
version: 3.1.3
|
||||
framer-motion:
|
||||
specifier: 11.0.3
|
||||
version: 11.0.3(react-dom@18.2.0)(react@18.2.0)
|
||||
gpt-tokenizer:
|
||||
specifier: 2.1.2
|
||||
version: 2.1.2
|
||||
|
|
@ -1856,6 +1859,20 @@ packages:
|
|||
resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==}
|
||||
dev: false
|
||||
|
||||
/@emotion/is-prop-valid@0.8.8:
|
||||
resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
'@emotion/memoize': 0.7.4
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@emotion/memoize@0.7.4:
|
||||
resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/@emotion/memoize@0.8.1:
|
||||
resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==}
|
||||
dev: false
|
||||
|
|
@ -8332,6 +8349,24 @@ packages:
|
|||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||
dev: true
|
||||
|
||||
/framer-motion@11.0.3(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-6x2poQpIWBdbZwLd73w6cKZ1I9IEPIU94C6/Swp1Zt3LJ+sB5bPe1E2wC6EH5hSISXNkMJ4afH7AdwS7MrtkWw==}
|
||||
peerDependencies:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
tslib: 2.6.2
|
||||
optionalDependencies:
|
||||
'@emotion/is-prop-valid': 0.8.8
|
||||
dev: false
|
||||
|
||||
/fs-constants@1.0.0:
|
||||
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
|
||||
dev: false
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
import { domMax } from "framer-motion"
|
||||
|
||||
export default domMax
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
"use client"
|
||||
|
||||
import { LazyMotion, MotionConfig } from "framer-motion"
|
||||
import {
|
||||
IntlError,
|
||||
IntlErrorCode,
|
||||
|
|
@ -61,6 +62,9 @@ export const mantineDefaultColorScheme = mantineDefaultColorSchemeT as
|
|||
| "light"
|
||||
| "dark"
|
||||
|
||||
const loadFeatures = () =>
|
||||
import("./framer-lazy-feature").then((res) => res.default)
|
||||
|
||||
export default function Providers({ children }: { children: React.ReactNode }) {
|
||||
useMobileLayout()
|
||||
useNProgress()
|
||||
|
|
@ -97,7 +101,16 @@ export default function Providers({ children }: { children: React.ReactNode }) {
|
|||
signInStrategy="simple"
|
||||
ignoreWalletDisconnectEvent={true}
|
||||
>
|
||||
<ModalStackProvider>{children}</ModalStackProvider>
|
||||
<LazyMotion features={loadFeatures} strict key="framer">
|
||||
<MotionConfig
|
||||
transition={{
|
||||
type: "spring",
|
||||
duration: 0.3,
|
||||
}}
|
||||
>
|
||||
<ModalStackProvider>{children}</ModalStackProvider>
|
||||
</MotionConfig>
|
||||
</LazyMotion>
|
||||
<NotificationModal
|
||||
colorScheme={colorScheme}
|
||||
filter={filterNotification}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { AnimatePresence, m } from "framer-motion"
|
||||
import Link from "next/link"
|
||||
import React, { Fragment } from "react"
|
||||
|
||||
|
|
@ -35,15 +36,31 @@ export function Menu({
|
|||
<HeadlessUiMenu.Button as={Fragment}>
|
||||
{React.cloneElement(target, { ref: refs.setReference })}
|
||||
</HeadlessUiMenu.Button>
|
||||
<HeadlessUiMenu.Items
|
||||
ref={refs.setFloating}
|
||||
className={cn(
|
||||
"absolute z-10 mt-1 w-max outline-none text-gray-600 bg-white rounded-lg ring-1 ring-border shadow-md py-2",
|
||||
)}
|
||||
style={floatingStyles}
|
||||
>
|
||||
{dropdown}
|
||||
</HeadlessUiMenu.Items>
|
||||
<AnimatePresence>
|
||||
<HeadlessUiMenu.Items
|
||||
className="absolute z-10 w-max"
|
||||
ref={refs.setFloating}
|
||||
style={floatingStyles}
|
||||
>
|
||||
<m.div
|
||||
className="mt-1 outline-none text-gray-600 bg-white rounded-lg ring-1 ring-border shadow-md py-2"
|
||||
initial={{
|
||||
translateY: "10px",
|
||||
opacity: 0,
|
||||
}}
|
||||
animate={{
|
||||
translateY: "0px",
|
||||
opacity: 1,
|
||||
}}
|
||||
exit={{
|
||||
translateY: "10px",
|
||||
opacity: 0,
|
||||
}}
|
||||
>
|
||||
{dropdown}
|
||||
</m.div>
|
||||
</HeadlessUiMenu.Items>
|
||||
</AnimatePresence>
|
||||
</HeadlessUiMenu>
|
||||
)
|
||||
}
|
||||
|
|
@ -71,7 +88,7 @@ Menu.Item = function MenuItem({
|
|||
const childElement = (
|
||||
<>
|
||||
<span
|
||||
className="mr-2 fill-gray-500 flex items-center w-4 h-4 text-base leading-none"
|
||||
className="mr-2 fill-gray-500 flex items-center size-4 text-base leading-none"
|
||||
aria-hidden
|
||||
>
|
||||
{icon}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
"use client"
|
||||
|
||||
import {
|
||||
AnimatePresence,
|
||||
m,
|
||||
useMotionValue,
|
||||
useMotionValueEvent,
|
||||
} from "framer-motion"
|
||||
import { useTranslations } from "next-intl"
|
||||
import React, { forwardRef, Fragment } from "react"
|
||||
import React, { forwardRef } from "react"
|
||||
|
||||
import { Dialog, Transition } from "@headlessui/react"
|
||||
import { Dialog } from "@headlessui/react"
|
||||
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
|
|
@ -41,46 +47,57 @@ export const Modal = forwardRef<HTMLDivElement, ModalProps>(
|
|||
ref,
|
||||
) => {
|
||||
const t = useTranslations()
|
||||
const x = useMotionValue(0)
|
||||
|
||||
useMotionValueEvent(x, "animationComplete", () => {
|
||||
console.log("animation Complete on x")
|
||||
afterLeave?.()
|
||||
})
|
||||
|
||||
return (
|
||||
<Transition appear show={open} as={Fragment} afterLeave={afterLeave}>
|
||||
<Dialog
|
||||
onClose={() => setOpen(false)}
|
||||
className="relative"
|
||||
style={{
|
||||
zIndex: zIndex ? zIndex : 10,
|
||||
}}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-100"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
className="relative"
|
||||
style={{
|
||||
zIndex: zIndex ? zIndex : 10,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
<m.div
|
||||
className="fixed inset-0 bg-black/25 z-40"
|
||||
aria-hidden={true}
|
||||
style={{ x }}
|
||||
initial={{
|
||||
opacity: 0,
|
||||
}}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
}}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
}}
|
||||
/>
|
||||
</Transition.Child>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"fixed inset-0 flex items-center justify-center p-8 z-40",
|
||||
boxClassName,
|
||||
)}
|
||||
ref={ref}
|
||||
>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-100"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-100"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
<m.div
|
||||
className={cn(
|
||||
"fixed inset-0 flex items-center justify-center p-8 z-40",
|
||||
boxClassName,
|
||||
)}
|
||||
ref={ref}
|
||||
initial={{
|
||||
opacity: 0,
|
||||
scale: 1.1,
|
||||
}}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
}}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
scale: 1.1,
|
||||
}}
|
||||
>
|
||||
{/* The actual dialog panel */}
|
||||
<Dialog.Panel
|
||||
|
|
@ -101,7 +118,7 @@ export const Modal = forwardRef<HTMLDivElement, ModalProps>(
|
|||
{title}
|
||||
</span>
|
||||
<span
|
||||
className="absolute right-4 w-7 h-7 text-xl cursor-pointer bg-white flex items-center justify-center"
|
||||
className="absolute right-4 size-7 text-xl cursor-pointer bg-white flex items-center justify-center"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<i className="i-mingcute-close-line inline-block" />
|
||||
|
|
@ -118,10 +135,10 @@ export const Modal = forwardRef<HTMLDivElement, ModalProps>(
|
|||
</Button>
|
||||
)}
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
</m.div>
|
||||
</Dialog>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client"
|
||||
|
||||
import { AnimatePresence, m } from "framer-motion"
|
||||
import { useTranslations } from "next-intl"
|
||||
import { usePathname } from "next/navigation"
|
||||
import React from "react"
|
||||
|
|
@ -49,38 +50,47 @@ export const Tabs = ({
|
|||
className,
|
||||
)}
|
||||
>
|
||||
{items.map((item) => {
|
||||
if (item.hidden) return null
|
||||
<AnimatePresence>
|
||||
{items.map((item) => {
|
||||
if (item.hidden) return null
|
||||
|
||||
return (
|
||||
<UniLink
|
||||
href={item.href}
|
||||
onClick={item.onClick}
|
||||
key={item.text}
|
||||
className={cn(
|
||||
"inline-flex items-center h-10 whitespace-nowrap cursor-pointer transition-colors focus-ring relative",
|
||||
type === "rounded"
|
||||
? "rounded-full h-8 px-3 transition-colors"
|
||||
: "after:absolute after:h-[2px] after:transition-[left,right] after:bottom-0",
|
||||
type === "rounded"
|
||||
? item.active
|
||||
? "bg-zinc-950 text-white"
|
||||
: "bg-zinc-100 text-zinc-800 hover:bg-zinc-200"
|
||||
: item.active
|
||||
? "text-black font-medium after:inset-x-0 after:bg-accent"
|
||||
: "text-gray-500 hover:text-gray-700 after:inset-x-1/2 hover:after:inset-x-0 after:bg-gray-700",
|
||||
)}
|
||||
>
|
||||
{item.tooltip ? (
|
||||
<Tooltip label={item.tooltip}>
|
||||
return (
|
||||
<UniLink
|
||||
href={item.href}
|
||||
onClick={item.onClick}
|
||||
key={item.text}
|
||||
className={cn(
|
||||
"inline-flex items-center h-10 whitespace-nowrap cursor-pointer transition-colors focus-ring relative",
|
||||
type === "rounded"
|
||||
? "rounded-full h-8 px-3 transition-colors"
|
||||
: "",
|
||||
type === "rounded"
|
||||
? item.active
|
||||
? "bg-zinc-950 text-white"
|
||||
: "bg-zinc-100 text-zinc-800 hover:bg-zinc-200"
|
||||
: item.active
|
||||
? "text-accent font-medium"
|
||||
: "text-gray-600 hover:text-accent",
|
||||
)}
|
||||
>
|
||||
{item.tooltip ? (
|
||||
<Tooltip label={item.tooltip}>
|
||||
<>{t(item.text)}</>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<>{t(item.text)}</>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<>{t(item.text)}</>
|
||||
)}
|
||||
</UniLink>
|
||||
)
|
||||
})}
|
||||
)}
|
||||
{type !== "rounded" && item.active && (
|
||||
<m.span
|
||||
className="absolute inset-x-0 bottom-0 bg-accent h-[2px]"
|
||||
layout
|
||||
layoutId="tab-underline"
|
||||
></m.span>
|
||||
)}
|
||||
</UniLink>
|
||||
)
|
||||
})}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client"
|
||||
|
||||
import { AnimatePresence, m } from "framer-motion"
|
||||
import { useState } from "react"
|
||||
|
||||
import {
|
||||
|
|
@ -58,7 +59,7 @@ export const Tooltip = ({
|
|||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative">
|
||||
<div
|
||||
ref={refs.setReference}
|
||||
{...getReferenceProps()}
|
||||
|
|
@ -70,24 +71,29 @@ export const Tooltip = ({
|
|||
>
|
||||
{children}
|
||||
</div>
|
||||
{isMounted && (
|
||||
<div
|
||||
ref={refs.setFloating}
|
||||
className={cn(
|
||||
"bg-zinc-600 text-white rounded-lg shadow-lg px-3 py-1 whitespace-nowrap",
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
position: strategy,
|
||||
top: y ?? "0",
|
||||
left: x ?? "0",
|
||||
...styles,
|
||||
}}
|
||||
{...getFloatingProps()}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
<AnimatePresence>
|
||||
{isMounted && (
|
||||
<m.div
|
||||
ref={refs.setFloating}
|
||||
className={cn(
|
||||
"bg-zinc-600 text-white rounded-lg shadow-lg px-3 py-1 whitespace-nowrap",
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
position: strategy,
|
||||
top: y ?? "0",
|
||||
left: x ?? "0",
|
||||
...styles,
|
||||
}}
|
||||
initial={{ translateY: "10px", opacity: 0 }}
|
||||
animate={{ translateY: "0px", opacity: 1 }}
|
||||
exit={{ translateY: "10px", opacity: 0 }}
|
||||
{...getFloatingProps()}
|
||||
>
|
||||
{label}
|
||||
</m.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue