feat: smoother type tab

This commit is contained in:
DIYgod 2024-04-12 10:20:09 +08:00
parent be6375f7a6
commit 07e76259cb
No known key found for this signature in database
4 changed files with 65 additions and 25 deletions

View File

@ -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"

View File

@ -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'}

View File

@ -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>
)

View File

@ -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)
}