Skip to content

Commit 6b2fd40

Browse files
committed
Use loading spinner for loading status
1 parent 51300f1 commit 6b2fd40

File tree

6 files changed

+58
-6
lines changed

6 files changed

+58
-6
lines changed

packages/clerk-js/src/core/resources/OrganizationCreationDefaults.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ export class OrganizationCreationDefaults extends BaseResource implements Organi
1818
name: string;
1919
slug: string;
2020
logo: string | null;
21+
blurHash: string | null;
2122
} = {
2223
name: '',
2324
slug: '',
2425
logo: null,
26+
blurHash: null,
2527
};
2628

2729
public constructor(data: OrganizationCreationDefaultsJSON | OrganizationCreationDefaultsJSONSnapshot | null = null) {
@@ -42,6 +44,7 @@ export class OrganizationCreationDefaults extends BaseResource implements Organi
4244
this.form.name = this.withDefault(data.form.name, this.form.name);
4345
this.form.slug = this.withDefault(data.form.slug, this.form.slug);
4446
this.form.logo = this.withDefault(data.form.logo, this.form.logo);
47+
this.form.blurHash = this.withDefault(data.form.blur_hash, this.form.blurHash);
4548
}
4649

4750
return this;

packages/shared/src/types/organizationCreationDefaults.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface OrganizationCreationDefaultsJSON extends ClerkResourceJSON {
1515
name: string;
1616
slug: string;
1717
logo: string | null;
18+
blur_hash: string | null;
1819
};
1920
}
2021

@@ -28,5 +29,6 @@ export interface OrganizationCreationDefaultsResource extends ClerkResource {
2829
name: string;
2930
slug: string;
3031
logo: string | null;
32+
blurHash: string | null;
3133
};
3234
}

packages/ui/src/components/OrganizationProfile/OrganizationProfileAvatarUploader.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ import { Col, descriptors, Text } from '../../customizables';
88
import { localizationKeys } from '../../localization';
99

1010
export const OrganizationProfileAvatarUploader = (
11-
props: Omit<AvatarUploaderProps, 'avatarPreview' | 'title'> & { organization: Partial<OrganizationResource> },
11+
props: Omit<AvatarUploaderProps, 'avatarPreview' | 'title'> & {
12+
organization: Partial<OrganizationResource>;
13+
/** Shows a loading spinner while the image is loading */
14+
showLoadingSpinner?: boolean;
15+
},
1216
) => {
13-
const { organization, ...rest } = props;
17+
const { organization, showLoadingSpinner, ...rest } = props;
1418

1519
return (
1620
<Col elementDescriptor={descriptors.organizationAvatarUploaderContainer}>
@@ -28,6 +32,7 @@ export const OrganizationProfileAvatarUploader = (
2832
avatarPreview={
2933
<OrganizationAvatar
3034
size={theme => theme.sizes.$16}
35+
showLoadingSpinner={showLoadingSpinner}
3136
{...organization}
3237
/>
3338
}

packages/ui/src/components/SessionTasks/tasks/TaskChooseOrganization/CreateOrganizationScreen.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export const CreateOrganizationScreen = (props: CreateOrganizationScreenProps) =
118118
organization={{ name: nameField.value, imageUrl: defaultLogoUrl ?? undefined }}
119119
onAvatarChange={async file => await setFile(file)}
120120
onAvatarRemove={file || defaultLogoUrl ? onAvatarRemove : null}
121+
showLoadingSpinner={!!defaultLogoUrl}
121122
avatarPreviewPlaceholder={
122123
<IconButton
123124
variant='ghost'

packages/ui/src/elements/Avatar.tsx

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22

3-
import { Box, descriptors, Flex, Image, Text } from '../customizables';
3+
import { Box, descriptors, Flex, Image, Spinner, Text } from '../customizables';
44
import type { ElementDescriptor } from '../customizables/elementDescriptors';
55
import type { InternalTheme } from '../foundations';
66
import type { PropsOfComponent } from '../styledSystem';
@@ -15,6 +15,8 @@ type AvatarProps = PropsOfComponent<typeof Flex> & {
1515
rounded?: boolean;
1616
boxElementDescriptor?: ElementDescriptor;
1717
imageElementDescriptor?: ElementDescriptor;
18+
/** Shows a loading spinner while the image is loading */
19+
showLoadingSpinner?: boolean;
1820
};
1921

2022
export const Avatar = (props: AvatarProps) => {
@@ -28,8 +30,18 @@ export const Avatar = (props: AvatarProps) => {
2830
sx,
2931
boxElementDescriptor,
3032
imageElementDescriptor,
33+
showLoadingSpinner = false,
3134
} = props;
3235
const [error, setError] = React.useState(false);
36+
const [loaded, setLoaded] = React.useState(false);
37+
38+
// Reset loaded state when imageUrl changes
39+
React.useEffect(() => {
40+
setLoaded(false);
41+
setError(false);
42+
}, [imageUrl]);
43+
44+
const isLoading = showLoadingSpinner && imageUrl && !loaded && !error;
3345

3446
const ImgOrFallback =
3547
initials && (!imageUrl || error) ? (
@@ -40,8 +52,15 @@ export const Avatar = (props: AvatarProps) => {
4052
title={title}
4153
alt={`${title}'s logo`}
4254
src={imageUrl || ''}
43-
sx={{ objectFit: 'cover', width: '100%', height: '100%' }}
55+
sx={{
56+
objectFit: 'cover',
57+
width: '100%',
58+
height: '100%',
59+
opacity: showLoadingSpinner ? (loaded ? 1 : 0) : 1,
60+
transition: 'opacity 0.2s ease-in-out',
61+
}}
4462
onError={() => setError(true)}
63+
onLoad={() => setLoaded(true)}
4564
size={imageFetchSize}
4665
/>
4766
);
@@ -67,6 +86,24 @@ export const Avatar = (props: AvatarProps) => {
6786
>
6887
{ImgOrFallback}
6988

89+
{isLoading && (
90+
<Flex
91+
as='span'
92+
sx={t => ({
93+
position: 'absolute',
94+
inset: 0,
95+
alignItems: 'center',
96+
justifyContent: 'center',
97+
backgroundColor: t.colors.$avatarBackground,
98+
})}
99+
>
100+
<Spinner
101+
size='sm'
102+
colorScheme='neutral'
103+
/>
104+
</Flex>
105+
)}
106+
70107
{/* /**
71108
* This Box is the "shimmer" effect for the avatar.
72109
* The ":after" selector is responsible for the border shimmer animation.

packages/ui/src/elements/OrganizationAvatar.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@ import type { PropsOfComponent } from '../styledSystem';
44
import { Avatar } from './Avatar';
55

66
type OrganizationAvatarProps = PropsOfComponent<typeof Avatar> &
7-
Partial<Pick<OrganizationResource, 'name' | 'imageUrl'>>;
7+
Partial<Pick<OrganizationResource, 'name' | 'imageUrl'>> & {
8+
/** Shows a loading spinner while the image is loading */
9+
showLoadingSpinner?: boolean;
10+
};
811

912
export const OrganizationAvatar = (props: OrganizationAvatarProps) => {
10-
const { name = '', imageUrl, ...rest } = props;
13+
const { name = '', imageUrl, showLoadingSpinner, ...rest } = props;
1114
return (
1215
<Avatar
1316
title={name}
1417
initials={(name || ' ')[0]}
1518
imageUrl={imageUrl}
19+
showLoadingSpinner={showLoadingSpinner}
1620
rounded={false}
1721
{...rest}
1822
/>

0 commit comments

Comments
 (0)