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位验证码" // 可选:自定义标题
            />
        </>
    );
}

预览

预览地址

相关推荐
ywf121532 分钟前
前端的dist包放到后端springboot项目下一起打包
前端·spring boot·后端
恋猫de小郭39 分钟前
2026,Android Compose 终于支持 Hot Reload 了,但是收费
android·前端·flutter
hpoenixf7 小时前
2026 年前端面试问什么
前端·面试
还是大剑师兰特7 小时前
Vue3 中的 defineExpose 完全指南
前端·javascript·vue.js
泯泷7 小时前
阶段一:从 0 看懂 JSVMP 架构,先在脑子里搭出一台最小 JSVM
前端·javascript·架构
mengchanmian8 小时前
前端node常用配置
前端
华洛8 小时前
利好打工人,openclaw不是企业提效工具,而是个人助理
前端·javascript·产品经理
xkxnq8 小时前
第六阶段:Vue生态高级整合与优化(第93天)Element Plus进阶:自定义主题(变量覆盖)+ 全局配置与组件按需加载优化
前端·javascript·vue.js
A黄俊辉A9 小时前
vue css中 :global的使用
前端·javascript·vue.js
小码哥_常9 小时前
被EdgeToEdge适配折磨疯了,谁懂!
前端