You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求助:Popover.Panel首次淡入异常及Headless UI Dropdown定位问题

问题解决方案

一、Popover.Panel首次点击右侧淡入问题

原因

  1. 同时混用Headless UI Popover内置定位和第三方usePopper,两者定位逻辑冲突,首次打开时popperElement未初始化,导致位置计算异常。
  2. 面板内部嵌套的div额外添加absolute类,干扰了Popover的定位层级。

解决步骤

  1. 移除手动引入的usePopper相关代码,Headless UI Popover本身已集成定位功能,无需额外依赖。
  2. 去掉Popover.Panel上的style={styles.popper}和{...attributes.popper}属性。
  3. 移除面板内部div的absolute类,避免定位冲突。
  4. 调整Transition的enterFrom类,使用translate-y-2替代scale-95,让面板从上方平滑淡入,消除右侧偏移感。

二、Headless UI Dropdown定位对齐问题

原因

  1. 错误混用外部定位库会干扰Dropdown的内置定位逻辑。
  2. 面板的自定义样式(如margin、absolute)会破坏与按钮的对齐关系。

解决步骤

  1. 依赖Dropdown组件自身的定位配置,通过placement="bottom"设置面板在按钮下方,align="start"让面板与按钮左侧对齐(或align="center"居中对齐)。
  2. 确保Dropdown.Button是Dropdown组件的直接子元素,作为定位参考。
  3. 避免给Dropdown.Menu添加额外的absolute、margin等可能干扰定位的样式。

修改后的完整代码

import React, { useState } from "react";
import useGlobalStore from "../../../../context/useGlobalStore";
import dayjs from "dayjs";
import {
  CaretRight,
  CaretLeft,
  ArrowLeft,
  ArrowRight,
  Calendar,
} from "phosphor-react";
import { Popover, Transition } from "@headlessui/react";

export default function Datepicker() {
  const [isCalendarOpen, setIsCalendarOpen] = useState(false);

  function toggleCalendar() {
    setIsCalendarOpen(!isCalendarOpen);
  }

  const { currentDay, incrementDay, decrementDay, isToday } = useGlobalStore(
    (state) => ({
      currentDay: state.currentDay,
      incrementDay: state.incrementDay,
      decrementDay: state.decrementDay,
      isToday: state.isToday(),
    })
  );

  const formatDate = (date) => {
    const today = dayjs();

    if (date.isSame(today, "day")) {
      return "Today";
    } else if (date.isSame(today.add(1, "day"), "day")) {
      return "Tomorrow";
    } else {
      return "Selected date";
    }
  };

  const [currentMonth, setCurrentMonth] = useState(new Date());

  const getDaysInMonth = (year, month) =>
    new Date(year, month + 1, 0).getDate();
  const getFirstDayOfWeek = (year, month) => new Date(year, month, 1).getDay();

  const daysInMonth = getDaysInMonth(
    currentMonth.getFullYear(),
    currentMonth.getMonth()
  );
  const firstDayOfWeek = getFirstDayOfWeek(
    currentMonth.getFullYear(),
    currentMonth.getMonth()
  );

  const isPreviousMonthDisabled = () => {
    const today = new Date();
    return (
      currentMonth.getMonth() === today.getMonth() &&
      currentMonth.getFullYear() === today.getFullYear()
    );
  };

  const renderCalendar = () => {
    const days = [];
    for (let day = 1; day <= daysInMonth; day++) {
      days.push(
        <td key={day}>
          <div className="px-2 py-2 cursor-pointer flex hover:bg-slate-100 w-full justify-center">
            <p className="text-base text-gray-500 font-medium">{day}</p>
          </div>
        </td>
      );
    }

    const weeks = [];
    let week = [];
    for (let i = 0; i < firstDayOfWeek; i++) {
      week.push(<td key={`empty-${i}`} />);
    }

    days.forEach((day, index) => {
      week.push(day);
      if ((index + firstDayOfWeek + 1) % 7 === 0 || index === days.length - 1) {
        weeks.push(<tr key={`week-${weeks.length}`}>{week}</tr>);
        week = [];
      }
    });

    return weeks;
  };

  const handlePrevMonth = () => {
    setCurrentMonth(
      new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1)
    );
  };

  const handleNextMonth = () => {
    setCurrentMonth(
      new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1)
    );
  };

  return (
    <>
      <Popover className="relative flex text-left">
        <Popover.Button>
          <div className="px-4 py-2 rounded-l-lg flex gap-2 items-center border border-gray-200 border-r-0 hover:bg-gray-200 hover:cursor-pointer hover:duration-100 duration-100">
            <Calendar size={20} />
            <p className="font-normal text-md">{formatDate(currentDay)}</p>
          </div>
        </Popover.Button>
        <p className="border border-gray-200 bg-stone-50 flex items-center gap-3 text-slate-900 rounded-r-lg px-4 py-2 text-sm">
          {!isToday ? (
            <>
              <ArrowLeft
                size={20}
                weight="bold"
                className="text-gray-500 hover:text-slate-900 duration-100 hover:duration-100 hover:cursor-pointer"
                onClick={decrementDay}
              />
            </>
          ) : (
            <>
              <ArrowLeft
                size={20}
                weight="bold"
                className="text-gray-200 duration-100 hover:duration-100 hover:cursor-not-allowed"
              />
            </>
          )}
          <p className="">{currentDay.format("MM/DD/YYYY")}</p>
          <ArrowRight
            size={20}
            weight="bold"
            className="text-gray-500 hover:text-slate-900 duration-100 hover:duration-100 hover:cursor-pointer"
            onClick={incrementDay}
          />
        </p>

        <Transition
          as={React.Fragment}
          enter="transition ease-out duration-200"
          enterFrom="transform opacity-0 translate-y-2"
          enterTo="transform opacity-100 translate-y-0"
          leave="transition ease-in duration-150"
          leaveFrom="transform opacity-100 translate-y-0"
          leaveTo="transform opacity-0 translate-y-2"
        >
          <Popover.Panel
            className="z-10 absolute rounded-md shadow-xl bg-white focus:outline-none"
            placement="bottom"
            align="start"
          >
            <div className="px-8 py-6 mt-2 bg-white max-w-sm shadow-xl rounded-md border">
              <div className="flex items-center justify-between">
                <div className="flex items-center justify-between w-full">
                  <button
                    aria-label="calendar backward"
                    className={`focus:text-gray-400 hover:text-gray-400 text-gray-800 ${
                      isPreviousMonthDisabled() === true
                        ? "cursor-not-allowed"
                        : ""
                    }`}
                    onClick={handlePrevMonth}
                    disabled={isPreviousMonthDisabled()}
                  >
                    <CaretLeft size={20} />
                  </button>
                  <span className="focus:outline-none text-base font-bold text-gray-800">
                    {currentMonth.toLocaleString("en-US", {
                      month: "long",
                      year: "numeric",
                    })}
                  </span>
                  <button
                    aria-label="calendar forward"
                    className="focus:text-gray-400 hover:text-gray-400 ml-3 text-gray-800"
                    onClick={handleNextMonth}
                  >
                    <CaretRight size={20} />
                  </button>
                </div>
              </div>
              <div className="flex items-center justify-between pt-8 overflow-x-auto">
                <table className="w-full">
                  <thead>
                    <tr>
                      {["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"].map((day) => (
                        <th key={day}>
                          <div className="w-full flex justify-center">
                            <p className="text-base font-medium text-center text-gray-800 pb-4">
                              {day}
                            </p>
                          </div>
                        </th>
                      ))}
                    </tr>
                  </thead>
                  <tbody>{renderCalendar()}</tbody>
                </table>
              </div>
            </div>
          </Popover.Panel>
        </Transition>
      </Popover>
    </>
  );
}

内容的提问来源于stack exchange,提问作者Paul Nicholas Chinnam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 05:38:10