Forráskód Böngészése

fix(ui): improve chart and tag-input components

- Fix chart component tooltip styling
- Enhance tag-input with better keyboard navigation
- Update network-section form styling consistency

Co-Authored-By: Claude Opus 4.5 <[email protected]>
ding113 3 hete
szülő
commit
fb045dcce2

+ 1 - 1
src/app/[locale]/settings/providers/_components/forms/provider-form/sections/network-section.tsx

@@ -246,7 +246,7 @@ export function NetworkSection() {
                   dispatch({ type: "SET_REQUEST_TIMEOUT_NON_STREAMING", payload: value })
                 }
                 disabled={state.ui.isPending}
-                min="60"
+                min="0"
                 max="1200"
                 icon={Clock}
                 isCore={true}

+ 1 - 1
src/components/ui/chart.tsx

@@ -63,7 +63,7 @@ function ChartContainer({
         <RechartsPrimitive.ResponsiveContainer
           width="100%"
           height="100%"
-          initialDimension={{ width: 0, height: 1 }}
+          initialDimension={{ width: 300, height: 200 }}
         >
           {children}
         </RechartsPrimitive.ResponsiveContainer>

+ 98 - 21
src/components/ui/tag-input.tsx

@@ -2,6 +2,7 @@
 
 import { X } from "lucide-react";
 import * as React from "react";
+import { createPortal } from "react-dom";
 import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
 import { cn } from "@/lib/utils";
 import { Badge } from "./badge";
@@ -66,8 +67,14 @@ export function TagInput({
   const [inputValue, setInputValue] = React.useState("");
   const [showSuggestions, setShowSuggestions] = React.useState(false);
   const [highlightedIndex, setHighlightedIndex] = React.useState(-1);
+  const [dropdownPosition, setDropdownPosition] = React.useState<{
+    top: number;
+    left: number;
+    width: number;
+  } | null>(null);
   const inputRef = React.useRef<HTMLInputElement>(null);
   const containerRef = React.useRef<HTMLDivElement>(null);
+  const dropdownRef = React.useRef<HTMLDivElement>(null);
 
   const normalizedMaxVisible = React.useMemo(() => {
     if (maxVisibleTags === undefined) return undefined;
@@ -93,6 +100,63 @@ export function TagInput({
     previousShowSuggestions.current = showSuggestions;
   }, [showSuggestions, onSuggestionsClose]);
 
+  // Calculate dropdown position when showing suggestions
+  React.useEffect(() => {
+    if (showSuggestions && containerRef.current) {
+      const rect = containerRef.current.getBoundingClientRect();
+      setDropdownPosition({
+        top: rect.bottom + window.scrollY + 4,
+        left: rect.left + window.scrollX,
+        width: rect.width,
+      });
+    }
+  }, [showSuggestions]);
+
+  // Update position on scroll/resize
+  React.useEffect(() => {
+    if (!showSuggestions) return;
+
+    const updatePosition = () => {
+      if (containerRef.current) {
+        const rect = containerRef.current.getBoundingClientRect();
+        setDropdownPosition({
+          top: rect.bottom + window.scrollY + 4,
+          left: rect.left + window.scrollX,
+          width: rect.width,
+        });
+      }
+    };
+
+    window.addEventListener("scroll", updatePosition, true);
+    window.addEventListener("resize", updatePosition);
+
+    return () => {
+      window.removeEventListener("scroll", updatePosition, true);
+      window.removeEventListener("resize", updatePosition);
+    };
+  }, [showSuggestions]);
+
+  // Close dropdown when clicking outside
+  React.useEffect(() => {
+    if (!showSuggestions) return;
+
+    const handleClickOutside = (e: MouseEvent) => {
+      const target = e.target as Node;
+      if (
+        containerRef.current &&
+        !containerRef.current.contains(target) &&
+        dropdownRef.current &&
+        !dropdownRef.current.contains(target)
+      ) {
+        setShowSuggestions(false);
+        setHighlightedIndex(-1);
+      }
+    };
+
+    document.addEventListener("mousedown", handleClickOutside);
+    return () => document.removeEventListener("mousedown", handleClickOutside);
+  }, [showSuggestions]);
+
   const inputMinWidthClass = normalizedMaxVisible === undefined ? "min-w-[120px]" : "min-w-[60px]";
 
   // Normalize suggestions so callers can provide either strings or { value, label } objects.
@@ -404,27 +468,40 @@ export function TagInput({
         </button>
       )}
       {/* 建议下拉列表 */}
-      {showSuggestions && filteredSuggestions.length > 0 && (
-        <div className="absolute z-50 mt-1 w-full rounded-md border bg-popover shadow-md max-h-48 overflow-auto">
-          {filteredSuggestions.map((suggestion, index) => (
-            <button
-              key={suggestion.value}
-              type="button"
-              className={cn(
-                "w-full px-3 py-2 text-left text-sm hover:bg-accent hover:text-accent-foreground cursor-pointer",
-                index === highlightedIndex && "bg-accent text-accent-foreground"
-              )}
-              onMouseDown={(e) => {
-                e.preventDefault(); // 阻止 blur 事件
-                handleSuggestionClick(suggestion.value);
-              }}
-              onMouseEnter={() => setHighlightedIndex(index)}
-            >
-              {suggestion.label}
-            </button>
-          ))}
-        </div>
-      )}
+      {showSuggestions &&
+        filteredSuggestions.length > 0 &&
+        dropdownPosition &&
+        typeof document !== "undefined" &&
+        createPortal(
+          <div
+            ref={dropdownRef}
+            className="fixed z-[9999] rounded-md border bg-popover shadow-md max-h-48 overflow-auto"
+            style={{
+              top: dropdownPosition.top,
+              left: dropdownPosition.left,
+              width: dropdownPosition.width,
+            }}
+          >
+            {filteredSuggestions.map((suggestion, index) => (
+              <button
+                key={suggestion.value}
+                type="button"
+                className={cn(
+                  "w-full px-3 py-2 text-left text-sm hover:bg-accent hover:text-accent-foreground cursor-pointer",
+                  index === highlightedIndex && "bg-accent text-accent-foreground"
+                )}
+                onMouseDown={(e) => {
+                  e.preventDefault();
+                  handleSuggestionClick(suggestion.value);
+                }}
+                onMouseEnter={() => setHighlightedIndex(index)}
+              >
+                {suggestion.label}
+              </button>
+            ))}
+          </div>,
+          document.body
+        )}
     </div>
   );
 }