mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-10-15 13:21:11 +00:00
feat(ui): smart truncation tooltips, org name limits, and logo UI polish (#5345)
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { standardSchemaResolver as zodResolver } from "@hookform/resolvers/standard-schema";
|
||||
|
||||
import { GlobeIcon, PenBoxIcon, Plus, X } from "lucide-react";
|
||||
import { PenBoxIcon, Plus, X } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import { z } from "zod";
|
||||
import { Logo } from "@/components/shared/logo";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -255,8 +256,10 @@ export function AddOrganization({
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem className="tems-center gap-4">
|
||||
<FormLabel className="text-right">Name</FormLabel>
|
||||
<FormItem className="items-center gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<FormLabel className="text-right">Name</FormLabel>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Organization name"
|
||||
@@ -285,16 +288,16 @@ export function AddOrganization({
|
||||
<FormControl>
|
||||
<div className="col-span-3 flex flex-col gap-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-md border bg-muted/50 p-1">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-md border bg-muted/50 overflow-hidden">
|
||||
{field.value ? (
|
||||
// biome-ignore lint/performance/noImgElement: user uploaded logo preview
|
||||
<img
|
||||
src={field.value}
|
||||
alt="Logo preview"
|
||||
className="size-full object-contain"
|
||||
className="size-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<GlobeIcon className="size-5 text-muted-foreground" />
|
||||
<Logo className="size-7" />
|
||||
)}
|
||||
</div>
|
||||
<div className="relative flex-1">
|
||||
|
||||
@@ -43,6 +43,7 @@ import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { TruncateTooltip } from "@/components/shared/truncate-tooltip";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Breadcrumb,
|
||||
@@ -656,9 +657,12 @@ function SidebarLogo() {
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-1.5 min-w-0 w-full">
|
||||
<p className="text-sm font-medium truncate">
|
||||
{activeOrganization?.name ?? "Select Organization"}
|
||||
</p>
|
||||
<TruncateTooltip
|
||||
text={
|
||||
activeOrganization?.name ?? "Select Organization"
|
||||
}
|
||||
className="text-sm font-medium"
|
||||
/>
|
||||
{haveValidLicense && (
|
||||
<Badge variant="blue" className="shrink-0">
|
||||
Enterprise
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Tooltip as TooltipPrimitive } from "radix-ui";
|
||||
import type React from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface Props extends React.HTMLAttributes<HTMLParagraphElement> {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export const TruncateTooltip = ({ text, className, ...props }: Props) => {
|
||||
const textRef = useRef<HTMLParagraphElement>(null);
|
||||
const [isTruncated, setIsTruncated] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const element = textRef.current;
|
||||
if (!element) return;
|
||||
|
||||
const checkTruncation = () => {
|
||||
const truncated = element.scrollWidth > element.clientWidth;
|
||||
setIsTruncated(truncated);
|
||||
if (!truncated) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
checkTruncation();
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
checkTruncation();
|
||||
});
|
||||
|
||||
resizeObserver.observe(element);
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
}, [text]);
|
||||
|
||||
const content = (
|
||||
<p ref={textRef} className={cn("truncate", className)} {...props}>
|
||||
{text}
|
||||
</p>
|
||||
);
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Tooltip
|
||||
delayDuration={0}
|
||||
open={isOpen}
|
||||
onOpenChange={(open) => {
|
||||
// Only allow opening if it's actually truncated
|
||||
if (isTruncated) {
|
||||
setIsOpen(open);
|
||||
} else {
|
||||
setIsOpen(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TooltipTrigger asChild>{content}</TooltipTrigger>
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
side="bottom"
|
||||
align="start"
|
||||
className="z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md max-w-[280px] break-words animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 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"
|
||||
>
|
||||
<p>{text}</p>
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
@@ -25,7 +25,7 @@ export const organizationRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
name: z.string().min(1),
|
||||
logo: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
@@ -130,7 +130,7 @@ export const organizationRouter = createTRPCRouter({
|
||||
.input(
|
||||
z.object({
|
||||
organizationId: z.string(),
|
||||
name: z.string(),
|
||||
name: z.string().min(1),
|
||||
logo: z.string().optional(),
|
||||
defaultRole: z.string().min(1).nullable().optional(),
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user