Improve fallback of Group Avatars not loading
This commit is contained in:
parent
e5cf1dfa85
commit
36be68cdcc
5 changed files with 88 additions and 28 deletions
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import GroupHeaderImage from 'soapbox/features/group/components/group-header-image';
|
||||
import GroupMemberCount from 'soapbox/features/group/components/group-member-count';
|
||||
import GroupPrivacy from 'soapbox/features/group/components/group-privacy';
|
||||
import GroupRelationship from 'soapbox/features/group/components/group-relationship';
|
||||
|
@ -10,17 +10,11 @@ import { HStack, Stack, Text } from './ui';
|
|||
|
||||
import type { Group as GroupEntity } from 'soapbox/types/entities';
|
||||
|
||||
const messages = defineMessages({
|
||||
groupHeader: { id: 'group.header.alt', defaultMessage: 'Group header' },
|
||||
});
|
||||
|
||||
interface IGroupCard {
|
||||
group: GroupEntity
|
||||
}
|
||||
|
||||
const GroupCard: React.FC<IGroupCard> = ({ group }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
className='relative h-[240px] rounded-lg border border-solid border-gray-300 bg-white dark:border-primary-800 dark:bg-primary-900'
|
||||
|
@ -28,12 +22,10 @@ const GroupCard: React.FC<IGroupCard> = ({ group }) => {
|
|||
>
|
||||
{/* Group Cover Image */}
|
||||
<Stack grow className='relative basis-1/2 rounded-t-lg bg-primary-100 dark:bg-gray-800'>
|
||||
{group.header && (
|
||||
<img
|
||||
className='absolute inset-0 h-full w-full rounded-t-lg object-cover'
|
||||
src={group.header} alt={intl.formatMessage(messages.groupHeader)}
|
||||
/>
|
||||
)}
|
||||
<GroupHeaderImage
|
||||
group={group}
|
||||
className='absolute inset-0 h-full w-full rounded-t-lg object-cover'
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
{/* Group Avatar */}
|
||||
|
|
|
@ -3,7 +3,7 @@ import React, { useRef } from 'react';
|
|||
|
||||
import { useSettings } from 'soapbox/hooks';
|
||||
|
||||
interface IStillImage {
|
||||
export interface IStillImage {
|
||||
/** Image alt text. */
|
||||
alt?: string
|
||||
/** Extra class names for the outer <div> container. */
|
||||
|
|
|
@ -1,34 +1,54 @@
|
|||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import StillImage from 'soapbox/components/still-image';
|
||||
import StillImage, { IStillImage } from 'soapbox/components/still-image';
|
||||
|
||||
import Icon from '../icon/icon';
|
||||
|
||||
const AVATAR_SIZE = 42;
|
||||
|
||||
interface IAvatar {
|
||||
/** URL to the avatar image. */
|
||||
src: string
|
||||
interface IAvatar extends Pick<IStillImage, 'src' | 'onError' | 'className'> {
|
||||
/** Width and height of the avatar in pixels. */
|
||||
size?: number
|
||||
/** Extra class names for the div surrounding the avatar image. */
|
||||
className?: string
|
||||
}
|
||||
|
||||
/** Round profile avatar for accounts. */
|
||||
const Avatar = (props: IAvatar) => {
|
||||
const { src, size = AVATAR_SIZE, className } = props;
|
||||
|
||||
const [isAvatarMissing, setIsAvatarMissing] = useState<boolean>(false);
|
||||
|
||||
const handleLoadFailure = () => setIsAvatarMissing(true);
|
||||
|
||||
const style: React.CSSProperties = React.useMemo(() => ({
|
||||
width: size,
|
||||
height: size,
|
||||
}), [size]);
|
||||
|
||||
if (isAvatarMissing) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
}}
|
||||
className={clsx('flex items-center justify-center rounded-full bg-gray-200 dark:bg-gray-900', className)}
|
||||
>
|
||||
<Icon
|
||||
src={require('@tabler/icons/photo-off.svg')}
|
||||
className='h-4 w-4 text-gray-500 dark:text-gray-700'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StillImage
|
||||
className={clsx('rounded-full', className)}
|
||||
style={style}
|
||||
src={src}
|
||||
alt='Avatar'
|
||||
onError={handleLoadFailure}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
50
app/soapbox/features/group/components/group-header-image.tsx
Normal file
50
app/soapbox/features/group/components/group-header-image.tsx
Normal file
|
@ -0,0 +1,50 @@
|
|||
import clsx from 'clsx';
|
||||
import React, { useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { Icon } from 'soapbox/components/ui';
|
||||
|
||||
import type { Group } from 'soapbox/schemas';
|
||||
|
||||
const messages = defineMessages({
|
||||
header: { id: 'group.header.alt', defaultMessage: 'Group header' },
|
||||
});
|
||||
|
||||
interface IGroupHeaderImage {
|
||||
group?: Group | false | null
|
||||
className?: string
|
||||
}
|
||||
|
||||
const GroupHeaderImage: React.FC<IGroupHeaderImage> = ({ className, group }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const [isHeaderMissing, setIsHeaderMissing] = useState<boolean>(false);
|
||||
|
||||
if (!group || !group.header) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isHeaderMissing) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(className, 'flex items-center justify-center bg-gray-200 dark:bg-gray-800/30')}
|
||||
>
|
||||
<Icon
|
||||
src={require('@tabler/icons/photo-off.svg')}
|
||||
className='h-6 w-6 text-gray-500 dark:text-gray-700'
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
className={className}
|
||||
src={group.header}
|
||||
alt={intl.formatMessage(messages.header)}
|
||||
onError={() => setIsHeaderMissing(true)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default GroupHeaderImage;
|
|
@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
|
|||
import GroupAvatar from 'soapbox/components/groups/group-avatar';
|
||||
import { HStack, Stack, Text } from 'soapbox/components/ui';
|
||||
import GroupActionButton from 'soapbox/features/group/components/group-action-button';
|
||||
import GroupHeaderImage from 'soapbox/features/group/components/group-header-image';
|
||||
import GroupMemberCount from 'soapbox/features/group/components/group-member-count';
|
||||
import GroupPrivacy from 'soapbox/features/group/components/group-privacy';
|
||||
|
||||
|
@ -31,13 +32,10 @@ const GroupGridItem = forwardRef((props: IGroup, ref: React.ForwardedRef<HTMLDiv
|
|||
ref={ref}
|
||||
style={{ minHeight: 180 }}
|
||||
>
|
||||
{group.header && (
|
||||
<img
|
||||
src={group.header}
|
||||
alt='Group cover'
|
||||
className='absolute inset-0 object-cover'
|
||||
/>
|
||||
)}
|
||||
<GroupHeaderImage
|
||||
group={group}
|
||||
className='absolute inset-0 object-cover'
|
||||
/>
|
||||
|
||||
<div
|
||||
className='absolute inset-x-0 bottom-0 flex justify-center rounded-b-lg bg-gradient-to-t from-gray-900 to-transparent pb-8 pt-12 transition-opacity duration-500'
|
||||
|
|
Loading…
Reference in a new issue