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

React中Flowbite Drawer组件left/bottom位置异常问题求助

Fix for WrapperDrawer Left/Bottom Placement Issues

Key Issues Identified

  1. Initial Visibility: The drawer wasn't hidden by default, leading to it being visible on load for left/bottom placement (since their default fixed positioning puts them in the viewport).
  2. Missing Placement Positioning: No explicit left-0/bottom-0 classes were added, causing Flowbite's Drawer to apply incorrect slide animations and positioning.
  3. Race Condition & Instance Management: Using useState for the Drawer instance caused unnecessary re-renders, and the instance wasn't reinitialized when placement changed.

Corrected Code

type CustomDrawerPropsType = {
  id: string;
  children: React.ReactNode;
  isOpen: boolean;
  setOpen?: (val: boolean) => void;
  showCloseButton: boolean;
};

const WrapperDrawer = (props: CustomDrawerPropsType & DrawerOptions) => {
  const targetEl = React.useRef<HTMLDivElement>(null);
  const drawer = React.useRef<DrawerInterface | null>(null);
  const DEFAULT_BACKDROP_STYLE = 'bg-gray-700/50 dark:bg-gray-900/80 fixed inset-0 z-30';

  const options: DrawerOptions = {
    placement: props.placement ?? 'right',
    backdrop: props.backdrop ?? true,
    bodyScrolling: props.bodyScrolling ?? false,
    edge: props.edge ?? false,
    edgeOffset: props.edgeOffset ?? '',
    backdropClasses: props.backdropClasses ?? DEFAULT_BACKDROP_STYLE,
    onShow: props.onShow ?? (() => {}),
    onToggle: props.onToggle ?? (() => {}),
    onHide: () => {
      props.onHide?.();
      props.setOpen?.(false); // Sync parent state when drawer is hidden
    },
  };

  const instanceOptions: InstanceOptions = {
    id: props.id,
    override: true,
  };

  // Initialize/reinitialize Drawer on mount or placement change
  React.useEffect(() => {
    if (targetEl.current) {
      // Cleanup existing instance if any
      if (drawer.current) {
        drawer.current.destroy();
      }
      drawer.current = new Drawer(targetEl.current, options, instanceOptions);
    }

    // Cleanup on unmount
    return () => {
      drawer.current?.destroy();
    };
  }, [props.placement]);

  // Sync drawer visibility with isOpen prop
  React.useEffect(() => {
    if (drawer.current) {
      props.isOpen ? drawer.current.show() : drawer.current.hide();
    }
  }, [props.isOpen]);

  // Get placement-specific position and size classes
  const getPlacementClasses = (): string => {
    switch (props.placement) {
      case 'top':
        return 'top-0 w-screen h-3/5';
      case 'bottom':
        return 'bottom-0 w-screen h-3/5';
      case 'left':
        return 'left-0 h-screen w-3/5';
      case 'right':
      default:
        return 'right-0 h-screen w-3/5';
    }
  };

  return (
    <div
      id={props.id}
      ref={targetEl}
      className={`fixed z-40 overflow-y-auto bg-white dark:bg-gray-800 hidden ${getPlacementClasses()}`}
      tabIndex={-1}
      aria-labelledby={`${props.id}-label`}
    >
      {/* Accessibility label */}
      <h5 id={`${props.id}-label`} className="sr-only">Drawer Menu</h5>

      {props.showCloseButton && (
        <button
          type="button"
          aria-controls={props.id}
          className="absolute right-2.5 top-2.5 inline-flex h-8 w-8 items-center justify-center rounded-lg bg-transparent text-sm text-gray-400 hover:bg-gray-200 hover:text-gray-900 dark:hover:bg-gray-600 dark:hover:text-white"
          onClick={() => {
            drawer.current?.hide();
          }}
        >
          <svg
            className="w-3 h-3"
            aria-hidden="true"
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 14 14"
          >
            <path
              stroke="currentColor"
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth="2"
              d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
            />
          </svg>
          <span className="sr-only">Close menu</span>
        </button>
      )}

      {props.children}
    </div>
  );
};

Key Changes Explained

  1. Initial Hidden Class: Added hidden to the drawer's className to ensure it's hidden on first render, preventing the initial visibility bug.
  2. Placement-Specific Position Classes: The getPlacementClasses function now adds left-0/bottom-0/right-0/top-0 to properly position the drawer based on placement, which Flowbite uses to apply correct slide animations.
  3. Drawer Instance as Ref: Switched from useState to useRef for the Drawer instance to avoid unnecessary re-renders and manage the instance more efficiently.
  4. Reinitialize on Placement Change: Added props.placement to the useEffect dependency array, so the Drawer is destroyed and recreated when placement changes, ensuring correct behavior.
  5. State Sync: Updated the onHide option to call props.setOpen(false) to sync the parent component's state with the drawer's visibility, and updated the close button to trigger hide() (which now syncs state).
  6. Accessibility Fix: Added a unique accessibility label tied to the drawer's ID for better screen reader support.

内容的提问来源于stack exchange,提问作者pra-bhu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 00:34:52