daisyUI 扩展之 pin input 组件开发,极致pin码输入框

daisyUI 是一个基于 Tailwind CSS 的组件库,它为你提供了大量预设的、美观且高度可定制的 UI 组件,例如按钮、卡片、表单、导航栏等等。无需编写复杂的 CSS,也无需记忆成堆的 Tailwind 工具类,即可轻松构建现代化界面。

为什么选择 daisyUI?

  • 极速开发 :通过预设组件,大幅减少编写 HTML 和 CSS 的时间,让你专注于业务逻辑。
  • 纯净 HTML :告别冗长的 class 列表!daisyUI 的组件类名简洁明了,让你的 HTML 结构更清晰、更易维护。
  • 高度可定制 :轻松通过 Tailwind CSS 的 tailwind.config.js 文件定制主题颜色、组件样式,完美契合你的品牌风格。
  • 丰富主题 :内置多种精美主题,一键切换,省时省力。
  • 轻量高效 :daisyUI 只会生成你实际用到的 CSS,保持最终产物的小巧。
  • 语义化类名 :例如 .btn-primary ,直观易懂。
  • 免费开源 :完全免费,并在 GitHub 上开源,拥有活跃的社区支持。

整体来说不管是响应式还是对 Tailwind CSS 的支持都非常完美,定制主题颜色、组件样式非常方便。也内置了多种精美主题。但是相对于antd、element-ui组件库,唯一不足之处,组件不是很丰富。部分组件需要自已扩展。

今天笔者分享的是 pin 码通用组件开发,费话不多说,直接上源码。

复制代码
import React, { useEffect, useRef, useState } from "react";

interface PinInputProps {
    isOpen: boolean;
    onClose: () => void;
    onComplete: (value: string) => void;
    length?: 4 | 6;
    title?: string;
}

const PinInput: React.FC<PinInputProps> = ({ isOpen, onClose, onComplete, length = 4, title = "请输入验证码" }) => {
    const [pins, setPins] = useState<string[]>(Array(length).fill(""));
    const inputRefs = useRef<(HTMLInputElement | null)[]>([]);

    useEffect(() => {
        if (isOpen) {
            // 重置输入状态
            setPins(Array(length).fill(""));
            // 聚焦第一个输入框
            setTimeout(() => {
                inputRefs.current[0]?.focus();
            }, 100);
        }
    }, [isOpen, length]);

    const handleInputChange = (index: number, value: string) => {
        if (value.length > 1) {
            value = value.slice(-1);
        }

        const newPins = [...pins];
        newPins[index] = value;
        setPins(newPins);

        // 自动跳转到下一个输入框
        if (value && index < length - 1) {
            inputRefs.current[index + 1]?.focus();
        }

        // 检查是否所有输入框都已填写
        if (newPins.every((pin) => pin !== "")) {
            onComplete(newPins.join(""));
        }
    };

    const handleKeyDown = (index: number, e: React.KeyboardEvent<HTMLInputElement>) => {
        if (e.key === "Backspace" && !pins[index] && index > 0) {
            // 如果当前输入框为空且按下退格键,则跳转到上一个输入框
            inputRefs.current[index - 1]?.focus();
        }
    };

    return (
        <dialog className={`modal w-auto ${isOpen ? "modal-open" : ""}`}>
            <div className="modal-box">
                <h3 className="mb-8 text-lg font-bold">{title}</h3>
                <div className="mb-8 flex justify-center gap-2">
                    {Array(length)
                        .fill(0)
                        .map((_, index) => (
                            <input
                                key={index}
                                ref={(el: HTMLInputElement | null) => {
                                    inputRefs.current[index] = el;
                                }}
                                type="text"
                                inputMode="numeric"
                                pattern="[0-9]*"
                                maxLength={1}
                                className="input input-bordered h-12 w-12 text-center text-lg"
                                value={pins[index]}
                                onChange={(e) => handleInputChange(index, e.target.value)}
                                onKeyDown={(e) => handleKeyDown(index, e)}
                            />
                        ))}
                </div>
                <div className="modal-action">
                    <button className="btn" onClick={onClose}>
                        取消
                    </button>
                </div>
            </div>
            <form method="dialog" className="modal-backdrop">
                <button onClick={onClose}>关闭</button>
            </form>
        </dialog>
    );
};

export default PinInput;

组件示例

复制代码
import { useState } from "react";
import PinInput from "@/components/PinInput";

export default function MyProfile() {
    const [isOpen, setIsOpen] = useState(false);

    const handleComplete = (value: string) => {
        console.log("验证码:", value);
        setIsOpen(false);
    };

    return (
        <>
            <button onClick={() => setIsOpen(true)}>输入验证码</button>

            <PinInput
                isOpen={isOpen}
                onClose={() => setIsOpen(false)}
                onComplete={handleComplete}
                length={6} // 可选:4 或 6
                title="请输入6位验证码" // 可选:自定义标题
            />
        </>
    );
}

预览

预览地址

相关推荐
—Qeyser5 分钟前
让 Deepseek 写电器电费计算器(html版本)
前端·javascript·css·html·deepseek
UI设计和前端开发从业者36 分钟前
从UI前端到数字孪生:构建数据驱动的智能生态系统
前端·ui
Junerver1 小时前
Kotlin 2.1.0的新改进带来哪些改变
前端·kotlin
千百元2 小时前
jenkins打包问题jar问题
前端
喝拿铁写前端2 小时前
前端批量校验还能这么写?函数式校验器组合太香了!
前端·javascript·架构
巴巴_羊2 小时前
6-16阿里前端面试记录
前端·面试·职场和发展
我是若尘2 小时前
前端遇到接口批量异常导致 Toast 弹窗轰炸该如何处理?
前端
该用户已不存在3 小时前
8个Docker的最佳替代方案,重塑你的开发工作流
前端·后端·docker
然我3 小时前
面试官最爱的 “考试思维”:用闭包秒杀递归难题 🚀
前端·javascript·面试
明月与玄武3 小时前
HTML知识全解析:从入门到精通的前端指南(上)
前端·html