feat: smoother type tab
This commit is contained in:
parent
be6375f7a6
commit
07e76259cb
|
|
@ -25,10 +25,12 @@
|
|||
"@electron-toolkit/utils": "^3.0.0",
|
||||
"@radix-ui/react-tabs": "1.0.4",
|
||||
"@radix-ui/react-tooltip": "1.0.7",
|
||||
"@use-gesture/react": "10.3.1",
|
||||
"class-variance-authority": "0.7.0",
|
||||
"clsx": "2.1.0",
|
||||
"electron-updater": "^6.1.7",
|
||||
"framer-motion": "11.0.27",
|
||||
"lethargy": "1.0.9",
|
||||
"react-router-dom": "6.22.3",
|
||||
"tailwind-merge": "2.2.2",
|
||||
"tailwindcss-animate": "1.0.7"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ dependencies:
|
|||
'@radix-ui/react-tooltip':
|
||||
specifier: 1.0.7
|
||||
version: 1.0.7(@types/react-dom@18.2.24)(@types/react@18.2.75)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@use-gesture/react':
|
||||
specifier: 10.3.1
|
||||
version: 10.3.1(react@18.2.0)
|
||||
class-variance-authority:
|
||||
specifier: 0.7.0
|
||||
version: 0.7.0
|
||||
|
|
@ -29,6 +32,9 @@ dependencies:
|
|||
framer-motion:
|
||||
specifier: 11.0.27
|
||||
version: 11.0.27(react-dom@18.2.0)(react@18.2.0)
|
||||
lethargy:
|
||||
specifier: 1.0.9
|
||||
version: 1.0.9
|
||||
react-router-dom:
|
||||
specifier: 6.22.3
|
||||
version: 6.22.3(react-dom@18.2.0)(react@18.2.0)
|
||||
|
|
@ -1954,6 +1960,19 @@ packages:
|
|||
eslint-visitor-keys: 3.4.3
|
||||
dev: true
|
||||
|
||||
/@use-gesture/core@10.3.1:
|
||||
resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==}
|
||||
dev: false
|
||||
|
||||
/@use-gesture/react@10.3.1(react@18.2.0):
|
||||
resolution: {integrity: sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==}
|
||||
peerDependencies:
|
||||
react: '>= 16.8.0'
|
||||
dependencies:
|
||||
'@use-gesture/core': 10.3.1
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/@vitejs/plugin-react@4.2.1(vite@5.2.8):
|
||||
resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
|
|
@ -4088,6 +4107,10 @@ packages:
|
|||
readable-stream: 2.3.8
|
||||
dev: true
|
||||
|
||||
/lethargy@1.0.9:
|
||||
resolution: {integrity: sha512-nFM8blpCF9rqIL5mRAaTGc78W8oQixVtsD86jbEPvcI13+lDUYJf3R7DZQQL7tCiBpbGpGKMX2gwJFO9hiaOkg==}
|
||||
dev: false
|
||||
|
||||
/levn@0.4.1:
|
||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@ import {
|
|||
TooltipProvider,
|
||||
TooltipTrigger
|
||||
} from '@renderer/components/ui/tooltip'
|
||||
import { m, useScroll, useMotionValueEvent } from 'framer-motion'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useWheel } from '@use-gesture/react'
|
||||
import { Lethargy } from 'lethargy'
|
||||
import { cn, clamp } from '@renderer/lib/utils'
|
||||
|
||||
function Content({ type }: { type: string }) {
|
||||
return (
|
||||
|
|
@ -15,6 +17,8 @@ function Content({ type }: { type: string }) {
|
|||
)
|
||||
}
|
||||
|
||||
const lethargy = new Lethargy()
|
||||
|
||||
export function TypeTab() {
|
||||
const items = [
|
||||
{
|
||||
|
|
@ -50,47 +54,54 @@ export function TypeTab() {
|
|||
]
|
||||
|
||||
const carouselRef = useRef<HTMLDivElement>(null)
|
||||
const { scrollXProgress } = useScroll({
|
||||
container: carouselRef
|
||||
})
|
||||
|
||||
const [xProgress, setXProgress] = useState(0)
|
||||
useMotionValueEvent(scrollXProgress, "change", (value) => {
|
||||
setXProgress(value);
|
||||
const [active, setActive] = useState(0)
|
||||
useWheel(({ event, last, memo: wait = false, direction: [dx], delta: [dex] }) => {
|
||||
if (!last) {
|
||||
const s = lethargy.check(event)
|
||||
if (s) {
|
||||
if (!wait && Math.abs(dex) > 20) {
|
||||
setActive((i) => clamp(i + dx, 0, items.length - 1))
|
||||
return true
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}, {
|
||||
target: carouselRef,
|
||||
})
|
||||
|
||||
return (
|
||||
<TooltipProvider delayDuration={300}>
|
||||
<div className="flex text-zinc-500 w-full justify-around text-xl my-2">
|
||||
{items.map((item, index) => (
|
||||
<m.div
|
||||
<div
|
||||
key={item.name}
|
||||
className={item.className}
|
||||
style={{
|
||||
filter: `grayscale(${Math.min(Math.abs(xProgress - 1 / (items.length - 1) * index) * (items.length - 1), 1) * 100}%)`,
|
||||
}}
|
||||
className={cn(item.className, active !== index && 'grayscale')}
|
||||
onClick={() => {
|
||||
if (carouselRef.current) {
|
||||
carouselRef.current.scrollTo({
|
||||
left: carouselRef.current.clientWidth * index,
|
||||
behavior: 'smooth'
|
||||
})
|
||||
}
|
||||
setActive(index)
|
||||
}}
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>{item.icon}</TooltipTrigger>
|
||||
<TooltipContent side="bottom">{item.name}</TooltipContent>
|
||||
</Tooltip>
|
||||
</m.div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="w-full h-full flex snap-x snap-mandatory overflow-x-auto" ref={carouselRef}>
|
||||
{items.map((item) => (
|
||||
<section key={item.name} className="snap-center shrink-0">
|
||||
<Content type={item.name} />
|
||||
</section>
|
||||
))}
|
||||
<div className="w-full h-full overflow-x-hidden" ref={carouselRef}>
|
||||
<div className="h-full flex transition-transform" style={{ transform: `translateX(${-active * 320}px)` }}>
|
||||
{items.map((item) => (
|
||||
<section key={item.name} className="snap-center shrink-0">
|
||||
<Content type={item.name} />
|
||||
</section>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,3 +4,7 @@ import { twMerge } from "tailwind-merge"
|
|||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export function clamp(value, min, max) {
|
||||
return Math.max(Math.min(max, value), min)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue