Commit 1a233366 by HoMeTown

feat: 输入框

parent be2a4fa0
......@@ -5,5 +5,6 @@ export default antfu({
react: true,
rules: {
'n/prefer-global/process': ['off'], // 关闭process报错
'react-hooks/exhaustive-deps': ['off'],
},
})
......@@ -52,6 +52,7 @@
"devDependencies": {
"@antfu/eslint-config": "^2.24.1",
"@eslint-react/eslint-plugin": "^1.8.0",
"@eslint/compat": "^1.1.1",
"@rsbuild/core": "1.0.1-beta.9",
"@rsbuild/plugin-less": "1.0.1-beta.9",
"@rsbuild/plugin-react": "1.0.1-beta.9",
......
'use client'
import { AnimatePresence, motion } from 'framer-motion'
import { useCallback, useEffect, useRef, useState } from 'react'
import { cn } from '@/lib/utils'
export function PlaceholdersInput({
placeholders,
onChange,
onSubmit,
}: {
placeholders: string[]
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void
}) {
const [currentPlaceholder, setCurrentPlaceholder] = useState(0)
const intervalRef = useRef<NodeJS.Timeout | null>(null)
const startAnimation = () => {
intervalRef.current = setInterval(() => {
setCurrentPlaceholder(prev => (prev + 1) % placeholders.length)
}, 3000)
}
const handleVisibilityChange = () => {
if (document.visibilityState !== 'visible' && intervalRef.current) {
clearInterval(intervalRef.current) // Clear the interval when the tab is not visible
intervalRef.current = null
}
else if (document.visibilityState === 'visible') {
startAnimation() // Restart the interval when the tab becomes visible
}
}
useEffect(() => {
startAnimation()
document.addEventListener('visibilitychange', handleVisibilityChange)
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current)
}
document.removeEventListener('visibilitychange', handleVisibilityChange)
}
}, [placeholders])
const canvasRef = useRef<HTMLCanvasElement>(null)
const newDataRef = useRef<any[]>([])
const inputRef = useRef<HTMLInputElement>(null)
const [value, setValue] = useState('')
const [animating, setAnimating] = useState(false)
const draw = useCallback(() => {
if (!inputRef.current)
return
const canvas = canvasRef.current
if (!canvas)
return
const ctx = canvas.getContext('2d')
if (!ctx)
return
canvas.width = 800
canvas.height = 800
ctx.clearRect(0, 0, 800, 800)
const computedStyles = getComputedStyle(inputRef.current)
const fontSize = Number.parseFloat(computedStyles.getPropertyValue('font-size'))
ctx.font = `${fontSize * 2}px ${computedStyles.fontFamily}`
ctx.fillStyle = '#FFF'
ctx.fillText(value, 16, 40)
const imageData = ctx.getImageData(0, 0, 800, 800)
const pixelData = imageData.data
const newData: any[] = []
for (let t = 0; t < 800; t++) {
const i = 4 * t * 800
for (let n = 0; n < 800; n++) {
const e = i + 4 * n
if (
pixelData[e] !== 0
&& pixelData[e + 1] !== 0
&& pixelData[e + 2] !== 0
) {
newData.push({
x: n,
y: t,
color: [
pixelData[e],
pixelData[e + 1],
pixelData[e + 2],
pixelData[e + 3],
],
})
}
}
}
newDataRef.current = newData.map(({ x, y, color }) => ({
x,
y,
r: 1,
color: `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3]})`,
}))
}, [value])
useEffect(() => {
draw()
}, [value, draw])
const animate = (start: number) => {
const animateFrame = (pos: number = 0) => {
requestAnimationFrame(() => {
const newArr = []
for (let i = 0; i < newDataRef.current.length; i++) {
const current = newDataRef.current[i]
if (current.x < pos) {
newArr.push(current)
}
else {
if (current.r <= 0) {
current.r = 0
continue
}
current.x += Math.random() > 0.5 ? 1 : -1
current.y += Math.random() > 0.5 ? 1 : -1
current.r -= 0.05 * Math.random()
newArr.push(current)
}
}
newDataRef.current = newArr
const ctx = canvasRef.current?.getContext('2d')
if (ctx) {
ctx.clearRect(pos, 0, 800, 800)
newDataRef.current.forEach((t) => {
const { x: n, y: i, r: s, color } = t
if (n > pos) {
ctx.beginPath()
ctx.rect(n, i, s, s)
ctx.fillStyle = color
ctx.strokeStyle = color
ctx.stroke()
}
})
}
if (newDataRef.current.length > 0) {
animateFrame(pos - 8)
}
else {
setValue('')
setAnimating(false)
}
})
}
animateFrame(start)
}
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' && !animating) {
// eslint-disable-next-line ts/no-use-before-define
vanishAndSubmit()
}
}
const vanishAndSubmit = () => {
setAnimating(true)
draw()
const value = inputRef.current?.value || ''
if (value && inputRef.current) {
const maxX = newDataRef.current.reduce(
(prev, current) => (current.x > prev ? current.x : prev),
0,
)
animate(maxX)
}
}
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
vanishAndSubmit()
// eslint-disable-next-line ts/no-unused-expressions
onSubmit && onSubmit(e)
}
return (
<form
className={cn(
'w-full relative mx-auto bg-white h-[62px] rounded-full overflow-hidden transition duration-200 sm:h-[72px]',
value && 'bg-gray-50',
)}
onSubmit={handleSubmit}
>
<input
onChange={(e) => {
if (!animating) {
setValue(e.target.value)
// eslint-disable-next-line ts/no-unused-expressions
onChange && onChange(e)
}
}}
onKeyDown={handleKeyDown}
ref={inputRef}
value={value}
type="text"
className={cn(
'w-full relative text-sm sm:text-base z-50 border-none dark:text-white bg-transparent text-black h-full rounded-full focus:outline-none focus:ring-0 pl-4 sm:pl-10 pr-20',
animating && 'text-transparent dark:text-transparent',
)}
/>
<button
disabled={!value}
type="submit"
className="absolute right-2 top-1/2 z-50 -translate-y-1/2 h-[40px] w-[40px] rounded-full disabled:bg-gray-100 bg-primary dark:bg-zinc-900 dark:disabled:bg-zinc-800 transition duration-200 flex items-center justify-center"
>
<motion.svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-white h-4 w-4"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<motion.path
d="M5 12l14 0"
initial={{
strokeDasharray: '50%',
strokeDashoffset: '50%',
}}
animate={{
strokeDashoffset: value ? 0 : '50%',
}}
transition={{
duration: 0.3,
ease: 'linear',
}}
/>
<path d="M13 18l6 -6" />
<path d="M13 6l6 6" />
</motion.svg>
</button>
<div className="absolute inset-0 flex items-center rounded-full pointer-events-none">
<AnimatePresence mode="wait">
{!value && (
<motion.p
initial={{
y: 5,
opacity: 0,
}}
key={`current-placeholder-${currentPlaceholder}`}
animate={{
y: 0,
opacity: 1,
}}
exit={{
y: -15,
opacity: 0,
}}
transition={{
duration: 0.3,
ease: 'linear',
}}
className="dark:text-zinc-500 text-[12px] sm:text-base font-normal text-[#3333334d] pl-4 sm:pl-12 text-left w-[calc(100%-2rem)] truncate"
>
{placeholders[currentPlaceholder]}
</motion.p>
)}
</AnimatePresence>
</div>
</form>
)
}
......@@ -7,8 +7,24 @@ import { Slogan } from './components/Slogan/Slogan'
import HomeIcon1 from '@/assets/homeIcon1.png'
import HomeIcon2 from '@/assets/homeIcon2.png'
import { GradientBackground } from '@/components/GradientBackground'
import { PlaceholdersInput } from '@/components/PlaceholdersInput'
export const Home: React.FC = () => {
const placeholders = [
'推荐几款60周岁还能投的医疗?',
'有哪些养老保险可以无脑入?',
'今年头部的保险公司有哪些好的产品?',
'直接开始问吧!',
'保险公司偿付能力在哪里可以看?',
]
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
e.preventDefault()
// console.log(e.target.value)
}
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
// console.log('submitted')
}
return (
<div className={styles.homePage}>
<GradientBackground />
......@@ -34,8 +50,18 @@ export const Home: React.FC = () => {
/>
</div>
<div className="mx-auto iptContainer w-full h-[132px] max-w-[1000px] flex-shrink-0">
input
<div className="box-border px-[0] mx-auto iptContainer w-full h-[122px] max-w-[1000px] flex-shrink-0 sm:px-0 sm:h-[132px]">
<PlaceholdersInput
placeholders={placeholders}
onChange={handleChange}
onSubmit={onSubmit}
/>
{/* <div className="w-full h-[72px] bg-white rounded-[36px]">
123
</div> */}
<div className="w-full text-center mt-[20px] text-[#3333334d] text-[12px]">
内容由AI模型生成,其准确性和完整性无法保证,仅供参考
</div>
</div>
</div>
</div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment