feat: add border to image hash placeholder

This commit is contained in:
codytseng
2025-12-24 22:48:38 +08:00
parent 41a65338b5
commit 526b64aec0
9 changed files with 28 additions and 26 deletions

View File

@@ -73,13 +73,13 @@ export default function Image({
}
return (
<div className={cn('relative overflow-hidden', classNames.wrapper)} {...props}>
<div className={cn('relative overflow-hidden rounded-xl', classNames.wrapper)} {...props}>
{/* Spacer: transparent image to maintain dimensions when image is loading */}
{isLoading && dim?.width && dim?.height && (
<img
src={`data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='${dim.width}' height='${dim.height}'%3E%3C/svg%3E`}
className={cn(
'object-cover rounded-xl transition-opacity pointer-events-none w-full h-full',
'object-cover transition-opacity pointer-events-none w-full h-full',
className
)}
alt=""
@@ -91,7 +91,7 @@ export default function Image({
<ThumbHashPlaceholder
thumbHash={thumbHash}
className={cn(
'w-full h-full transition-opacity rounded-xl',
'w-full h-full transition-opacity',
isLoading ? 'opacity-100' : 'opacity-0'
)}
/>
@@ -99,14 +99,14 @@ export default function Image({
<BlurHashCanvas
blurHash={blurHash}
className={cn(
'w-full h-full transition-opacity rounded-xl',
'w-full h-full transition-opacity',
isLoading ? 'opacity-100' : 'opacity-0'
)}
/>
) : (
<Skeleton
className={cn(
'w-full h-full transition-opacity rounded-xl',
'w-full h-full transition-opacity',
isLoading ? 'opacity-100' : 'opacity-0',
classNames.skeleton
)}
@@ -124,7 +124,7 @@ export default function Image({
onLoad={handleLoad}
onError={handleError}
className={cn(
'object-cover rounded-xl transition-opacity pointer-events-none w-full h-full',
'object-cover transition-opacity pointer-events-none w-full h-full',
isLoading ? 'opacity-0 absolute inset-0' : '',
className
)}
@@ -137,7 +137,7 @@ export default function Image({
alt={alt}
decoding="async"
loading="lazy"
className={cn('object-cover rounded-xl w-full h-full transition-opacity', className)}
className={cn('object-cover w-full h-full transition-opacity', className)}
/>
) : (
<div

View File

@@ -94,9 +94,9 @@ export default function ImageGallery({
<ImageWithLightbox
key={i}
image={image}
className="max-h-[80vh] sm:max-h-[50vh] object-contain border"
className="max-h-[80vh] sm:max-h-[50vh] object-contain"
classNames={{
wrapper: cn('w-fit max-w-full', className)
wrapper: cn('w-fit max-w-full border', className)
}}
/>
))
@@ -107,10 +107,10 @@ export default function ImageGallery({
imageContent = (
<Image
key={0}
className="max-h-[80vh] sm:max-h-[50vh] object-contain border"
className="max-h-[80vh] sm:max-h-[50vh] object-contain"
classNames={{
errorPlaceholder: 'aspect-square h-[30vh]',
wrapper: 'cursor-zoom-in'
wrapper: 'cursor-zoom-in border'
}}
image={displayImages[0]}
onClick={(e) => handlePhotoClick(e, 0)}
@@ -122,8 +122,8 @@ export default function ImageGallery({
{displayImages.map((image, i) => (
<Image
key={i}
className="aspect-square w-full border"
classNames={{ wrapper: 'cursor-zoom-in' }}
className="aspect-square w-full"
classNames={{ wrapper: 'cursor-zoom-in border' }}
image={image}
onClick={(e) => handlePhotoClick(e, i)}
/>
@@ -136,8 +136,8 @@ export default function ImageGallery({
{displayImages.map((image, i) => (
<Image
key={i}
className="aspect-square w-full border"
classNames={{ wrapper: 'cursor-zoom-in' }}
className="aspect-square w-full"
classNames={{ wrapper: 'cursor-zoom-in border' }}
image={image}
onClick={(e) => handlePhotoClick(e, i)}
/>

View File

@@ -67,7 +67,7 @@ export default function ImageWithLightbox({
key={0}
className={className}
classNames={{
wrapper: cn('rounded-lg border cursor-zoom-in', classNames.wrapper),
wrapper: cn('border cursor-zoom-in', classNames.wrapper),
errorPlaceholder: 'aspect-square h-[30vh]',
skeleton: classNames.skeleton
}}

View File

@@ -26,7 +26,7 @@ export default function FollowPack({ event, className }: { event: Event; classNa
{image && (
<Image
image={{ url: image, pubkey: event.pubkey }}
className="w-24 h-20 object-cover rounded-lg"
className="w-24 h-20 object-cover"
classNames={{
wrapper: 'w-24 h-20 flex-shrink-0',
errorPlaceholder: 'w-24 h-20'

View File

@@ -67,7 +67,7 @@ export default function LongFormArticlePreview({
{metadata.image && autoLoadMedia && (
<Image
image={{ url: metadata.image, pubkey: event.pubkey }}
className="rounded-lg aspect-[4/3] xl:aspect-video object-cover bg-foreground h-44"
className="aspect-[4/3] xl:aspect-video object-cover bg-foreground h-44"
hideIfError
/>
)}

View File

@@ -40,8 +40,8 @@ export function ReactionNotification({
<Image
image={{ url: emojiUrl, pubkey: notification.pubkey }}
alt={emojiName}
className="w-6 h-6 rounded-md"
classNames={{ errorPlaceholder: 'bg-transparent' }}
className="w-6 h-6"
classNames={{ errorPlaceholder: 'bg-transparent', wrapper: 'rounded-md' }}
errorPlaceholder={<Heart size={24} className="text-red-400" />}
/>
)

View File

@@ -1,5 +1,4 @@
import { generateImageByPubkey } from '@/lib/pubkey'
import { cn } from '@/lib/utils'
import { useEffect, useMemo, useState } from 'react'
import Image from '../Image'
@@ -27,7 +26,10 @@ export default function ProfileBanner({
<Image
image={{ url: bannerUrl, pubkey }}
alt={`${pubkey} banner`}
className={cn('rounded-none', className)}
className={className}
classNames={{
wrapper: 'rounded-none'
}}
errorPlaceholder={defaultBanner}
/>
)

View File

@@ -36,9 +36,9 @@ export default function RelayInfo({ url, className }: { url: string; className?:
<div className="px-4 space-y-4">
<div className="space-y-2">
<div className="flex items-center gap-2 justify-between">
<div className="flex gap-2 items-center truncate">
<div className="flex gap-2 items-center flex-1">
<RelayIcon url={url} className="w-8 h-8" />
<div className="text-2xl font-semibold truncate select-text">
<div className="text-2xl font-semibold truncate select-text flex-1 w-0">
{relayInfo.name || relayInfo.shortUrl}
</div>
</div>

View File

@@ -68,9 +68,9 @@ export default function WebPreview({
{image && (
<Image
image={{ url: image }}
className="aspect-[4/3] xl:aspect-video bg-foreground h-44 rounded-none border-r"
className="aspect-[4/3] xl:aspect-video bg-foreground h-44"
classNames={{
skeleton: 'rounded-none border-r'
wrapper: 'rounded-none border-r'
}}
hideIfError
/>