diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-12-24 12:18:08 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-12-24 10:18:08 +0000 |
| commit | 314c363e5ca69a50626650ade8968feec583e5ce (patch) | |
| tree | 2251691c2a79598b50b4417ee5632b602e5faf78 /apps/web/components/ui | |
| parent | 3408e6e4854dc79b963eef455e9a69231de3cd28 (diff) | |
| download | karakeep-314c363e5ca69a50626650ade8968feec583e5ce.tar.zst | |
feat: add support for user avatars (#2296)
* feat: add support for user avatars
* more fixes
* more fixes
* more fixes
* more fixes
Diffstat (limited to 'apps/web/components/ui')
| -rw-r--r-- | apps/web/components/ui/avatar.tsx | 49 | ||||
| -rw-r--r-- | apps/web/components/ui/user-avatar.tsx | 52 |
2 files changed, 101 insertions, 0 deletions
diff --git a/apps/web/components/ui/avatar.tsx b/apps/web/components/ui/avatar.tsx new file mode 100644 index 00000000..7afec626 --- /dev/null +++ b/apps/web/components/ui/avatar.tsx @@ -0,0 +1,49 @@ +"use client"; + +import * as React from "react"; +import { cn } from "@/lib/utils"; +import * as AvatarPrimitive from "@radix-ui/react-avatar"; + +const Avatar = React.forwardRef< + React.ElementRef<typeof AvatarPrimitive.Root>, + React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> +>(({ className, ...props }, ref) => ( + <AvatarPrimitive.Root + ref={ref} + className={cn( + "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", + className, + )} + {...props} + /> +)); +Avatar.displayName = AvatarPrimitive.Root.displayName; + +const AvatarImage = React.forwardRef< + React.ElementRef<typeof AvatarPrimitive.Image>, + React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> +>(({ className, ...props }, ref) => ( + <AvatarPrimitive.Image + ref={ref} + className={cn("aspect-square h-full w-full", className)} + {...props} + /> +)); +AvatarImage.displayName = AvatarPrimitive.Image.displayName; + +const AvatarFallback = React.forwardRef< + React.ElementRef<typeof AvatarPrimitive.Fallback>, + React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> +>(({ className, ...props }, ref) => ( + <AvatarPrimitive.Fallback + ref={ref} + className={cn( + "flex h-full w-full items-center justify-center rounded-full bg-muted", + className, + )} + {...props} + /> +)); +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/apps/web/components/ui/user-avatar.tsx b/apps/web/components/ui/user-avatar.tsx new file mode 100644 index 00000000..4ebb6ec3 --- /dev/null +++ b/apps/web/components/ui/user-avatar.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { useMemo } from "react"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { cn } from "@/lib/utils"; + +import { getAssetUrl } from "@karakeep/shared/utils/assetUtils"; + +interface UserAvatarProps { + image?: string | null; + name?: string | null; + className?: string; + imgClassName?: string; + fallbackClassName?: string; + fallback?: React.ReactNode; +} + +const isExternalUrl = (value: string) => + value.startsWith("http://") || value.startsWith("https://"); + +export function UserAvatar({ + image, + name, + className, + imgClassName, + fallbackClassName, + fallback, +}: UserAvatarProps) { + const avatarUrl = useMemo(() => { + if (!image) { + return null; + } + return isExternalUrl(image) ? image : getAssetUrl(image); + }, [image]); + + const fallbackContent = fallback ?? name?.charAt(0) ?? "U"; + + return ( + <Avatar className={className}> + {avatarUrl && ( + <AvatarImage + src={avatarUrl} + alt={name ?? "User"} + className={cn("object-cover", imgClassName)} + /> + )} + <AvatarFallback className={cn("text-sm font-medium", fallbackClassName)}> + {fallbackContent} + </AvatarFallback> + </Avatar> + ); +} |
