fix(frontend): prevent multiselect overflow and normalize card spacing (#152)

## Summary
- fix multi-select trigger overflow in shared select bridge by making
trigger/value flex children shrink correctly
- harden grouped assignment summaries against long selected-value text
wrapping overflow
- normalize card header/body spacing and node card IP row height so card
layouts stay visually consistent across modules

## Verification
- ran `npm run build` in `vite-frontend` successfully
- checked diagnostics on changed frontend files (no diagnostics)
This commit is contained in:
sagit
2026-02-19 18:23:28 +08:00
committed by GitHub
9 changed files with 25 additions and 25 deletions
+5 -5
View File
@@ -1260,9 +1260,9 @@ export default function ForwardPage() {
return (
<Card
key={forward.id}
className="group shadow-sm border border-divider hover:shadow-md transition-shadow duration-200"
className="group shadow-sm border border-divider hover:shadow-md transition-shadow duration-200 overflow-hidden"
>
<CardHeader className="pb-2">
<CardHeader className="pb-2 md:pb-2">
<div className="flex justify-between items-start w-full">
{selectMode && (
<Checkbox
@@ -1319,7 +1319,7 @@ export default function ForwardPage() {
</div>
</CardHeader>
<CardBody className="pt-0 pb-3">
<CardBody className="pt-0 pb-3 md:pt-0 md:pb-3">
<div className="space-y-2">
{/* 地址信息 */}
<div className="space-y-1">
@@ -1667,7 +1667,7 @@ export default function ForwardPage() {
key={userGroup.userId || "unknown"}
className="shadow-sm border border-divider w-full overflow-hidden"
>
<CardHeader className="pb-3">
<CardHeader className="pb-2 md:pb-2">
<div className="flex items-center justify-between w-full min-w-0">
<div className="flex items-center gap-3 min-w-0 flex-1">
<div className="w-10 h-10 bg-primary-100 dark:bg-primary-900/30 rounded-full flex items-center justify-center flex-shrink-0">
@@ -1709,7 +1709,7 @@ export default function ForwardPage() {
</div>
</CardHeader>
<CardBody className="pt-0">
<CardBody className="pt-0 md:pt-0">
<Accordion className="px-0" variant="splitted">
{userGroup.tunnelGroups.map((tunnelGroup) => (
<AccordionItem
+2 -2
View File
@@ -779,7 +779,7 @@ export default function GroupPage() {
>
{(item) => <SelectItem key={item.id}>{item.name}</SelectItem>}
</Select>
<p className="text-xs text-default-500">
<p className="text-xs text-default-500 break-all whitespace-normal">
当前已选:
{Array.from(selectedTunnelKeys)
.map((id) => tunnelNameMap.get(Number(id)) || id)
@@ -824,7 +824,7 @@ export default function GroupPage() {
>
{(item) => <SelectItem key={item.id}>{item.user}</SelectItem>}
</Select>
<p className="text-xs text-default-500">
<p className="text-xs text-default-500 break-all whitespace-normal">
当前已选:
{Array.from(selectedUserKeys)
.map((id) => userNameMap.get(Number(id)) || id)
+3 -3
View File
@@ -235,9 +235,9 @@ export default function LimitPage() {
{rules.map((rule) => (
<Card
key={rule.id}
className="shadow-sm border border-gray-200 dark:border-gray-700"
className="shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden"
>
<CardHeader className="pb-3">
<CardHeader className="pb-2 md:pb-2">
<div className="flex justify-between items-start w-full">
<div>
<h3 className="font-semibold text-foreground">
@@ -253,7 +253,7 @@ export default function LimitPage() {
</Chip>
</div>
</CardHeader>
<CardBody className="pt-0">
<CardBody className="pt-0 pb-3 md:pt-0 md:pb-3">
<div className="space-y-3">
<div className="flex justify-between items-center">
<span className="text-small text-default-600">
+1 -1
View File
@@ -1155,7 +1155,7 @@ export default function NodePage() {
<span className="text-default-600 flex-shrink-0">
IP
</span>
<div className="text-right text-xs min-w-0 flex-1 ml-2">
<div className="text-right text-xs min-w-0 flex-1 ml-2 min-h-[2.125rem]">
{node.serverIpV4?.trim() ||
node.serverIpV6?.trim() ? (
<div className="space-y-0.5">
+4 -4
View File
@@ -420,7 +420,7 @@ export default function PanelSharingPage() {
key={share.id}
className="border border-divider shadow-sm"
>
<CardHeader className="flex justify-between">
<CardHeader className="flex justify-between pb-2 md:pb-2">
<h3 className="font-bold">{share.name}</h3>
<div className="flex gap-2">
<Button
@@ -447,7 +447,7 @@ export default function PanelSharingPage() {
</Button>
</div>
</CardHeader>
<CardBody className="text-sm space-y-2">
<CardBody className="text-sm space-y-2 pt-0 md:pt-0">
<p>
端口范围: {share.portRangeStart} -{" "}
{share.portRangeEnd}
@@ -535,13 +535,13 @@ export default function PanelSharingPage() {
key={node.nodeId}
className="border border-divider shadow-sm"
>
<CardHeader className="flex justify-between">
<CardHeader className="flex justify-between pb-2 md:pb-2">
<h3 className="font-bold">{node.nodeName}</h3>
<span className="text-xs text-default-500">
绑定 {node.activeBindingNum || 0}
</span>
</CardHeader>
<CardBody className="text-sm space-y-2">
<CardBody className="text-sm space-y-2 pt-0 md:pt-0">
{node.syncError && (
<div className="px-2 py-1.5 rounded-md bg-warning-50 dark:bg-warning-100/10 text-warning-700 dark:text-warning-400 text-xs">
{node.syncError === "provider_share_deleted"
+3 -3
View File
@@ -740,9 +740,9 @@ export default function TunnelPage() {
{(listeners) => (
<Card
key={tunnel.id}
className="group shadow-sm border border-divider hover:shadow-md transition-shadow duration-200"
className="group shadow-sm border border-divider hover:shadow-md transition-shadow duration-200 overflow-hidden"
>
<CardHeader className="pb-2">
<CardHeader className="pb-2 md:pb-2">
<div className="flex justify-between items-start w-full">
{selectMode && (
<Checkbox
@@ -784,7 +784,7 @@ export default function TunnelPage() {
</div>
</CardHeader>
<CardBody className="pt-0 pb-3">
<CardBody className="pt-0 pb-3 md:pt-0 md:pb-3">
<div className="space-y-3">
{/* 拓扑结构 */}
<div className="pt-2 border-t border-divider">
+3 -3
View File
@@ -688,9 +688,9 @@ export default function UserPage() {
return (
<Card
key={user.id}
className="shadow-sm border border-divider hover:shadow-md transition-shadow duration-200"
className="shadow-sm border border-divider hover:shadow-md transition-shadow duration-200 overflow-hidden"
>
<CardHeader className="pb-2">
<CardHeader className="pb-2 md:pb-2">
<div className="flex justify-between items-start w-full">
<div className="flex-1 min-w-0">
<h3 className="font-semibold text-foreground truncate text-sm">
@@ -713,7 +713,7 @@ export default function UserPage() {
</div>
</CardHeader>
<CardBody className="pt-0 pb-3">
<CardBody className="pt-0 pb-3 md:pt-0 md:pb-3">
<div className="space-y-2">
{/* 流量信息 */}
<div className="space-y-1.5">
@@ -16,5 +16,5 @@ export function CardHeader({ className, ...props }: React.ComponentProps<"div">)
}
export function CardBody({ className, ...props }: React.ComponentProps<"div">) {
return <CardContent className={cn("p-4 md:p-6", className)} {...props} />;
return <CardContent className={cn("p-4 pt-0 md:p-6 md:pt-0", className)} {...props} />;
}
@@ -240,7 +240,7 @@ export function Select<T>({
{selectionMode === "multiple" ? (
<div
className={cn(
"w-full rounded-md border border-input bg-background shadow-sm",
"w-full overflow-hidden rounded-md border border-input bg-background shadow-sm",
isDisabled ? "cursor-not-allowed opacity-60" : "",
className,
)}
@@ -250,7 +250,7 @@ export function Select<T>({
aria-controls={`${generatedId}-listbox`}
aria-expanded={isExpanded}
className={cn(
"flex w-full items-center gap-2 px-3 py-2 text-left",
"flex w-full min-w-0 items-center gap-2 overflow-hidden px-3 py-2 text-left",
classNames?.trigger,
)}
disabled={isDisabled}
@@ -259,7 +259,7 @@ export function Select<T>({
>
<span
className={cn(
"block flex-1 truncate",
"block min-w-0 flex-1 truncate",
textSizeClass(size),
selectedArray.length > 0 ? "text-foreground" : "text-default-500",
)}