mirror of
https://github.com/fankes/komari-theme-purcarte.git
synced 2025-10-18 19:39:22 +08:00
fix: 修复4小时负载图表数据渲染和下拉菜单动画
This commit is contained in:
@@ -70,43 +70,49 @@ export const Header = ({
|
|||||||
<>
|
<>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<>
|
<>
|
||||||
<div
|
|
||||||
className={`absolute top-full left-0 w-full purcarte-blur p-2 border-b border-border shadow-sm z-10 transform transition-all duration-300 ease-in-out ${
|
|
||||||
isSearchOpen
|
|
||||||
? "opacity-100 translate-y-0"
|
|
||||||
: "opacity-0 -translate-y-4 pointer-events-none"
|
|
||||||
}`}>
|
|
||||||
<Input
|
|
||||||
type="search"
|
|
||||||
placeholder="搜索服务器..."
|
|
||||||
className="w-full"
|
|
||||||
value={searchTerm}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
setSearchTerm(e.target.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{enableSearchButton && (
|
{enableSearchButton && (
|
||||||
<Button
|
<DropdownMenu modal={false}>
|
||||||
variant="ghost"
|
<DropdownMenuTrigger asChild>
|
||||||
size="icon"
|
<Button
|
||||||
onClick={() => setIsSearchOpen(!isSearchOpen)}>
|
variant="ghost"
|
||||||
<Search className="size-5 text-primary" />
|
size="icon"
|
||||||
</Button>
|
className="relative group">
|
||||||
|
<Search className="size-5 text-primary" />
|
||||||
|
{searchTerm && (
|
||||||
|
<span className="absolute top-0 right-0 w-1.5 h-1.5 rounded-full bg-primary transform -translate-x-1/2"></span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent
|
||||||
|
align="end"
|
||||||
|
className="purcarte-blur border-border rounded-xl w-48">
|
||||||
|
<div className="p-2">
|
||||||
|
<Input
|
||||||
|
type="search"
|
||||||
|
placeholder="搜索服务器..."
|
||||||
|
className="w-full"
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => setSearchTerm(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
<DropdownMenu>
|
<DropdownMenu modal={false}>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="relative group">
|
className="relative group">
|
||||||
<Menu className="size-5 text-primary transition-transform duration-300 group-data-[state=open]:rotate-180" />
|
<Menu className="size-5 text-primary transition-transform duration-300 group-data-[state=open]:rotate-180" />
|
||||||
<span className="absolute -bottom-1 left-1/2 w-1.5 h-1.5 rounded-full bg-primary transform -translate-x-1/2 scale-0 transition-transform duration-300 group-data-[state=open]:scale-100"></span>
|
<span className="absolute top-0 right-0 w-1.5 h-1.5 rounded-full bg-primary transform -translate-x-1/2 scale-0 transition-transform duration-300 group-data-[state=open]:scale-100"></span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
align="end"
|
align="end"
|
||||||
className="animate-in slide-in-from-top-5 duration-300 purcarte-blur border-border rounded-xl">
|
className="purcarte-blur border-border rounded-xl">
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setViewMode(viewMode === "grid" ? "table" : "grid")
|
setViewMode(viewMode === "grid" ? "table" : "grid")
|
||||||
@@ -167,8 +173,12 @@ export const Header = ({
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
className="relative group"
|
||||||
onClick={() => setIsSearchOpen(!isSearchOpen)}>
|
onClick={() => setIsSearchOpen(!isSearchOpen)}>
|
||||||
<Search className="size-5 text-primary" />
|
<Search className="size-5 text-primary" />
|
||||||
|
{searchTerm && (
|
||||||
|
<span className="absolute top-0 right-0 w-1.5 h-1.5 rounded-full bg-primary transform -translate-x-1/2"></span>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
|
29
src/components/ui/dropdown-menu.css
Normal file
29
src/components/ui/dropdown-menu.css
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
@keyframes slideDownAndFade {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUpAndFade {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="menu"][data-state="open"] {
|
||||||
|
animation: slideDownAndFade 300ms cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="menu"][data-state="closed"] {
|
||||||
|
animation: slideUpAndFade 300ms cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
}
|
@@ -1,5 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import "./dropdown-menu.css";
|
||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
||||||
|
import { Theme } from "@radix-ui/themes";
|
||||||
import { DotFilledIcon } from "@radix-ui/react-icons";
|
import { DotFilledIcon } from "@radix-ui/react-icons";
|
||||||
|
|
||||||
import { cn } from "@/utils";
|
import { cn } from "@/utils";
|
||||||
@@ -43,7 +45,7 @@ const DropdownMenuSubContent = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.SubContent
|
<DropdownMenuPrimitive.SubContent
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 min-w-[8rem] purcarte-blur overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
"z-50 min-w-[8rem] purcarte-blur overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -57,15 +59,17 @@ const DropdownMenuContent = React.forwardRef<
|
|||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||||
<DropdownMenuPrimitive.Portal>
|
<DropdownMenuPrimitive.Portal>
|
||||||
<DropdownMenuPrimitive.Content
|
<Theme>
|
||||||
ref={ref}
|
<DropdownMenuPrimitive.Content
|
||||||
sideOffset={sideOffset}
|
ref={ref}
|
||||||
className={cn(
|
sideOffset={sideOffset}
|
||||||
"z-50 min-w-[8rem] purcarte-blur overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
className={cn(
|
||||||
className
|
"z-50 min-w-[8rem] purcarte-blur overflow-hidden rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md",
|
||||||
)}
|
className
|
||||||
{...props}
|
)}
|
||||||
/>
|
{...props}
|
||||||
|
/>
|
||||||
|
</Theme>
|
||||||
</DropdownMenuPrimitive.Portal>
|
</DropdownMenuPrimitive.Portal>
|
||||||
));
|
));
|
||||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
||||||
|
@@ -118,13 +118,20 @@ export const useLoadCharts = (node: NodeData | null, hours: number) => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
let filledData;
|
let filledData;
|
||||||
if (hours <= 4) {
|
if (hours === 1) {
|
||||||
filledData = fillMissingTimePoints(
|
filledData = fillMissingTimePoints(
|
||||||
stringifiedData,
|
stringifiedData,
|
||||||
minute,
|
minute,
|
||||||
hour,
|
hour,
|
||||||
minute * 2
|
minute * 2
|
||||||
);
|
);
|
||||||
|
} else if (hours === 4) {
|
||||||
|
filledData = fillMissingTimePoints(
|
||||||
|
stringifiedData,
|
||||||
|
minute,
|
||||||
|
hour * 4,
|
||||||
|
minute * 2
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const interval = hours > 120 ? hour : minute * 15;
|
const interval = hours > 120 ? hour : minute * 15;
|
||||||
const maxGap = interval * 2;
|
const maxGap = interval * 2;
|
||||||
|
Reference in New Issue
Block a user