light color for hover popups
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useHoverPreview } from '../utils/hoverPreviewUtils';
|
||||
import {
|
||||
useHoverPreview,
|
||||
getPreviewContainerClassName,
|
||||
} from '../utils/hoverPreviewUtils';
|
||||
|
||||
export interface HoverPreviewProps {
|
||||
children: ReactNode;
|
||||
@@ -41,13 +44,7 @@ export const HoverPreview: React.FC<HoverPreviewProps> = ({
|
||||
createPortal(
|
||||
<div
|
||||
ref={previewRef}
|
||||
className={[
|
||||
`max-w-[${maxWidth}] max-h-[${maxHeight}] rounded-lg border`,
|
||||
'border-slate-100 bg-white dark:border-slate-600',
|
||||
'divide-y divide-slate-100 dark:divide-slate-600',
|
||||
'shadow-xl shadow-slate-700/50',
|
||||
previewClassName,
|
||||
].join(' ')}
|
||||
className={`${getPreviewContainerClassName(maxWidth, maxHeight)} ${previewClassName}`}
|
||||
style={{
|
||||
...previewStyle,
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import { HoverPreview } from './HoverPreview';
|
||||
import {
|
||||
getHeaderFooterClassName,
|
||||
getContentClassName,
|
||||
getHeaderTextClassName,
|
||||
getSecondaryTextClassName,
|
||||
getBorderClassName,
|
||||
getIconClassName,
|
||||
getAvatarImageClassName,
|
||||
} from '../utils/hoverPreviewUtils';
|
||||
|
||||
interface PostHoverPreviewProps {
|
||||
children: React.ReactNode;
|
||||
@@ -20,37 +29,33 @@ export const PostHoverPreview: React.FC<PostHoverPreviewProps> = ({
|
||||
creatorName,
|
||||
creatorAvatarPath,
|
||||
}) => {
|
||||
const headerFooterClassName = [
|
||||
'flex items-center justify-between overflow-hidden',
|
||||
'border-slate-200 bg-gradient-to-r from-white to-slate-50 p-3',
|
||||
'dark:border-slate-600 dark:from-slate-800 dark:to-slate-700',
|
||||
].join(' ');
|
||||
// Add extra classes for PostHoverPreview's header/footer
|
||||
const postHeaderFooterClassName = `${getHeaderFooterClassName()} justify-between p-3`;
|
||||
|
||||
const previewContent = (
|
||||
<>
|
||||
{/* Header: Title */}
|
||||
<div className={headerFooterClassName}>
|
||||
<span
|
||||
className="mr-2 min-w-0 truncate text-sm font-medium tracking-tight text-slate-800 dark:text-slate-300"
|
||||
title={postTitle}
|
||||
>
|
||||
<div className={postHeaderFooterClassName}>
|
||||
<span className={getHeaderTextClassName()} title={postTitle}>
|
||||
{postTitle}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Image Content */}
|
||||
<div className="flex items-center justify-center bg-slate-50 p-2 dark:bg-slate-900">
|
||||
<div
|
||||
className={`flex items-center justify-center ${getContentClassName()} p-2`}
|
||||
>
|
||||
{postThumbnailPath ? (
|
||||
<div className="transition-transform hover:scale-[1.02]">
|
||||
<img
|
||||
src={postThumbnailPath}
|
||||
alt={postTitle}
|
||||
className="max-h-[250px] max-w-full rounded-md border border-slate-200 object-contain shadow-md dark:border-slate-600"
|
||||
className={`max-h-[250px] max-w-full rounded-md ${getBorderClassName()} object-contain shadow-md`}
|
||||
loading="eager"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<span className="block px-4 py-6 text-sm italic text-slate-500 dark:text-slate-400">
|
||||
<span className={`block px-4 py-6 ${getSecondaryTextClassName()}`}>
|
||||
{postThumbnailAlt || 'No file available'}
|
||||
</span>
|
||||
)}
|
||||
@@ -58,31 +63,31 @@ export const PostHoverPreview: React.FC<PostHoverPreviewProps> = ({
|
||||
|
||||
{/* Footer: Domain icon & Creator */}
|
||||
{creatorName && (
|
||||
<div className={headerFooterClassName}>
|
||||
{(postDomainIcon && (
|
||||
<img
|
||||
src={postDomainIcon}
|
||||
alt={postTitle}
|
||||
className="h-6 w-6 rounded-md bg-slate-500 p-1 shadow-sm ring-sky-100 dark:ring-sky-900"
|
||||
/>
|
||||
)) || (
|
||||
<span className="h-6 w-6 grow rounded-md shadow-sm ring-sky-100 dark:ring-sky-900"></span>
|
||||
)}
|
||||
<div className={postHeaderFooterClassName}>
|
||||
<span className="flex items-center gap-2 justify-self-end">
|
||||
<span
|
||||
className="truncate text-xs font-medium text-slate-500 dark:text-slate-400"
|
||||
title={creatorName}
|
||||
>
|
||||
{creatorName}
|
||||
</span>
|
||||
{creatorAvatarPath && (
|
||||
<img
|
||||
src={creatorAvatarPath}
|
||||
alt={creatorName}
|
||||
className="h-6 w-6 rounded-md shadow-sm ring-sky-100 dark:ring-sky-900"
|
||||
className={getAvatarImageClassName('small')}
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
className="truncate text-xs font-medium text-slate-700"
|
||||
title={creatorName}
|
||||
>
|
||||
{creatorName}
|
||||
</span>
|
||||
</span>
|
||||
{(postDomainIcon && (
|
||||
<img
|
||||
src={postDomainIcon}
|
||||
alt={postTitle}
|
||||
className={getIconClassName()}
|
||||
/>
|
||||
)) || (
|
||||
<span className="h-6 w-6 grow rounded-md shadow-sm ring-blue-200"></span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
import * as React from 'react';
|
||||
import { HoverPreview } from './HoverPreview';
|
||||
import {
|
||||
getHeaderFooterClassName,
|
||||
getContentClassName,
|
||||
getHeaderTextClassName,
|
||||
getLabelTextClassName,
|
||||
getValueTextClassName,
|
||||
getSecondaryTextClassName,
|
||||
getAvatarContainerClassName,
|
||||
getAvatarImageClassName,
|
||||
getIconClassName,
|
||||
} from '../utils/hoverPreviewUtils';
|
||||
|
||||
interface UserHoverPreviewProps {
|
||||
children: React.ReactNode;
|
||||
@@ -24,47 +35,40 @@ export const UserHoverPreview: React.FC<UserHoverPreviewProps> = ({
|
||||
userNumFollowedBy,
|
||||
userNumFollowed,
|
||||
}) => {
|
||||
const sectionClassName = [
|
||||
'flex items-center overflow-hidden',
|
||||
'border-slate-200 bg-gradient-to-r from-white to-slate-50 p-2',
|
||||
'dark:border-slate-600 dark:from-slate-800 dark:to-slate-700',
|
||||
].join(' ');
|
||||
|
||||
const previewContent = (
|
||||
<>
|
||||
{/* Header: User Name and Domain Icon */}
|
||||
<div className={`${sectionClassName} justify-between`}>
|
||||
<span
|
||||
className="mr-2 min-w-0 truncate text-sm font-semibold tracking-tight text-slate-800 dark:text-slate-300"
|
||||
title={userName}
|
||||
>
|
||||
<div className={`${getHeaderFooterClassName()} justify-between`}>
|
||||
<span className={getHeaderTextClassName()} title={userName}>
|
||||
{userName}
|
||||
</span>
|
||||
{userDomainIcon && (
|
||||
<img
|
||||
src={userDomainIcon}
|
||||
alt="Domain"
|
||||
className="h-5 w-5 rounded-md bg-slate-500 p-1 shadow-sm ring-sky-100 dark:ring-sky-900"
|
||||
className={getIconClassName()}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Profile Content */}
|
||||
<div className="bg-slate-50 p-3 dark:bg-slate-900">
|
||||
<div className={getContentClassName()}>
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Avatar */}
|
||||
<div className="flex-shrink-0">
|
||||
{userAvatarPath ? (
|
||||
<div className="overflow-hidden rounded-md">
|
||||
<div className={getAvatarContainerClassName()}>
|
||||
<img
|
||||
src={userAvatarPath}
|
||||
alt={userAvatarAlt}
|
||||
className="h-[80px] w-[80px] rounded-md border border-slate-200 object-cover shadow-sm dark:border-slate-600"
|
||||
className={getAvatarImageClassName('medium')}
|
||||
loading="eager"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<span className="block h-[80px] w-[80px] rounded-md border border-slate-200 bg-slate-100 p-2 text-xs italic text-slate-500 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-400">
|
||||
<span
|
||||
className={`block h-[80px] w-[80px] rounded-md border border-slate-300 bg-slate-200 p-2 text-xs italic ${getSecondaryTextClassName()}`}
|
||||
>
|
||||
No avatar
|
||||
</span>
|
||||
)}
|
||||
@@ -74,39 +78,35 @@ export const UserHoverPreview: React.FC<UserHoverPreviewProps> = ({
|
||||
<div className="flex-grow">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="text-center">
|
||||
<span className="block text-sm font-semibold text-slate-700 dark:text-slate-300">
|
||||
<span className={getValueTextClassName()}>
|
||||
{userNumPosts ?? '-'}
|
||||
</span>
|
||||
<span className="text-2xs text-slate-500 dark:text-slate-400">
|
||||
Posts
|
||||
</span>
|
||||
<span className={getLabelTextClassName()}>Posts</span>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<span className="block text-sm font-semibold text-slate-700 dark:text-slate-300">
|
||||
<span className={getValueTextClassName()}>
|
||||
{userNumFollowedBy ?? '-'}
|
||||
</span>
|
||||
<span className="text-2xs text-slate-500 dark:text-slate-400">
|
||||
Followers
|
||||
</span>
|
||||
<span className={getLabelTextClassName()}>Followers</span>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
<span className="block text-sm font-semibold text-slate-700 dark:text-slate-300">
|
||||
<span className={getValueTextClassName()}>
|
||||
{userNumFollowed ?? '-'}
|
||||
</span>
|
||||
<span className="text-2xs text-slate-500 dark:text-slate-400">
|
||||
Following
|
||||
</span>
|
||||
<span className={getLabelTextClassName()}>Following</span>
|
||||
</div>
|
||||
|
||||
<div className="text-center">
|
||||
{userRegisteredAt && (
|
||||
<>
|
||||
<span className="text-2xs block font-medium text-slate-700 dark:text-slate-300">
|
||||
<span
|
||||
className={`text-2xs block font-medium text-slate-800`}
|
||||
>
|
||||
Member since
|
||||
</span>
|
||||
<span className="text-2xs leading-tight text-slate-500 dark:text-slate-400">
|
||||
<span className={getLabelTextClassName()}>
|
||||
{userRegisteredAt}
|
||||
</span>
|
||||
</>
|
||||
|
||||
@@ -7,6 +7,79 @@ import {
|
||||
useMemo,
|
||||
} from 'react';
|
||||
|
||||
/**
|
||||
* Utility functions for hover preview styling
|
||||
*/
|
||||
|
||||
// Base preview container styling
|
||||
export const getPreviewContainerClassName = (
|
||||
maxWidth: string,
|
||||
maxHeight: string,
|
||||
) => {
|
||||
return [
|
||||
`max-w-[${maxWidth}] max-h-[${maxHeight}] rounded-lg`,
|
||||
'border border-slate-400 bg-slate-100',
|
||||
'divide-y divide-slate-300',
|
||||
'shadow-lg shadow-slate-500/30',
|
||||
].join(' ');
|
||||
};
|
||||
|
||||
// Header/Footer styling
|
||||
export const getHeaderFooterClassName = () => {
|
||||
return [
|
||||
'flex items-center overflow-hidden',
|
||||
'border-slate-400 bg-gradient-to-r from-slate-200 to-slate-300 p-2',
|
||||
].join(' ');
|
||||
};
|
||||
|
||||
// Content area styling
|
||||
export const getContentClassName = () => {
|
||||
return 'bg-slate-100 p-3';
|
||||
};
|
||||
|
||||
// Text styling functions
|
||||
export const getHeaderTextClassName = () => {
|
||||
return 'mr-2 min-w-0 truncate text-sm font-semibold text-slate-700';
|
||||
};
|
||||
|
||||
export const getLabelTextClassName = () => {
|
||||
return 'text-2xs text-slate-700';
|
||||
};
|
||||
|
||||
export const getValueTextClassName = () => {
|
||||
return 'block text-sm font-semibold text-slate-700';
|
||||
};
|
||||
|
||||
export const getSecondaryTextClassName = () => {
|
||||
return 'text-sm italic text-slate-600';
|
||||
};
|
||||
|
||||
// Border and shadow styling
|
||||
export const getBorderClassName = () => {
|
||||
return 'border border-slate-300';
|
||||
};
|
||||
|
||||
// Avatar container styling
|
||||
export const getAvatarContainerClassName = () => {
|
||||
return 'overflow-hidden rounded-md';
|
||||
};
|
||||
|
||||
// Base avatar image styling
|
||||
export const getAvatarImageClassName = (size: string = 'medium') => {
|
||||
const sizeClasses = {
|
||||
small: 'h-6 w-6',
|
||||
medium: 'h-[80px] w-[80px]',
|
||||
large: 'h-20 w-20',
|
||||
};
|
||||
|
||||
return `${sizeClasses[size as keyof typeof sizeClasses]} rounded-md ${getBorderClassName()} object-cover shadow-sm`;
|
||||
};
|
||||
|
||||
// Icon styling
|
||||
export const getIconClassName = () => {
|
||||
return 'h-8 w-8 rounded-md p-1';
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate the position for a hover preview popup
|
||||
* @param containerRef The reference to the element containing the hover trigger
|
||||
|
||||
Reference in New Issue
Block a user