diff --git a/.changeset/dull-stingrays-fix.md b/.changeset/dull-stingrays-fix.md new file mode 100644 index 00000000000..7c2e680bbab --- /dev/null +++ b/.changeset/dull-stingrays-fix.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Enhance `CreateOrganizationForm` by replacing `AvatarPreview` with an upload button to prevent layout shifts \ No newline at end of file diff --git a/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganizationForm.tsx b/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganizationForm.tsx index 0d89db40c5e..037f30bf8d9 100644 --- a/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganizationForm.tsx +++ b/packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganizationForm.tsx @@ -3,8 +3,9 @@ import React from 'react'; import { useWizard, Wizard } from '../../common'; import { useCoreOrganization, useCoreOrganizationList } from '../../contexts'; -import { ContentPage, Form, FormButtonContainer, SuccessPage, useCardState } from '../../elements'; -import { QuestionMark } from '../../icons'; +import { Icon } from '../../customizables'; +import { ContentPage, Form, FormButtonContainer, IconButton, SuccessPage, useCardState } from '../../elements'; +import { Image, QuestionMark, Upload } from '../../icons'; import type { LocalizationKey } from '../../localization'; import { localizationKeys } from '../../localization'; import { createSlug, handleError, useFormControl } from '../../utils'; @@ -27,6 +28,7 @@ type CreateOrganizationFormProps = { export const CreateOrganizationForm = (props: CreateOrganizationFormProps) => { const card = useCardState(); const wizard = useWizard({ onNextStep: () => card.setError(undefined) }); + const [isHovered, setIsHovered] = React.useState(false); const lastCreatedOrganizationRef = React.useRef(null); const { createOrganization, isLoaded, setActive } = useCoreOrganizationList(); @@ -121,6 +123,31 @@ export const CreateOrganizationForm = (props: CreateOrganizationFormProps) => { organization={{ name: nameField.value }} onAvatarChange={async file => await setFile(file)} onAvatarRemove={file ? onAvatarRemove : null} + avatarPreviewPlaceholder={ + + } + sx={theme => ({ + width: theme.sizes.$11, + height: theme.sizes.$11, + borderRadius: theme.radii.$md, + backgroundColor: theme.colors.$avatarBackground, + ':hover': { + backgroundColor: theme.colors.$avatarBackground, + opacity: 0.8, + }, + })} + onMouseEnter={() => setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + /> + } /> Promise; onAvatarRemove?: (() => void) | null; + avatarPreviewPlaceholder?: React.ReactElement | null; }; export const fileToBase64 = (file: File): Promise => { @@ -27,7 +28,7 @@ export const AvatarUploader = (props: AvatarUploaderProps) => { const [objectUrl, setObjectUrl] = React.useState(); const card = useCardState(); - const { onAvatarChange, onAvatarRemove, title, avatarPreview, ...rest } = props; + const { onAvatarChange, onAvatarRemove, title, avatarPreview, avatarPreviewPlaceholder, ...rest } = props; const toggle = () => { setShowUpload(!showUpload); @@ -54,6 +55,12 @@ export const AvatarUploader = (props: AvatarUploaderProps) => { return onAvatarRemove?.(); }; + const previewElement = objectUrl + ? React.cloneElement(avatarPreview, { imageUrl: objectUrl }) + : avatarPreviewPlaceholder + ? React.cloneElement(avatarPreviewPlaceholder, { onClick: toggle }) + : avatarPreview; + return ( { align='center' {...rest} > - {objectUrl ? React.cloneElement(avatarPreview, { imageUrl: objectUrl }) : avatarPreview} + {previewElement} \ No newline at end of file diff --git a/packages/clerk-js/src/ui/icons/index.ts b/packages/clerk-js/src/ui/icons/index.ts index 3f9a2b63611..a0b7ca9cd9f 100644 --- a/packages/clerk-js/src/ui/icons/index.ts +++ b/packages/clerk-js/src/ui/icons/index.ts @@ -7,41 +7,43 @@ export { default as ArrowLeftIcon } from './arrow-left.svg'; export { default as ArrowRightIcon } from './arrow-right.svg'; export { default as AuthApp } from './auth-app.svg'; -export { default as Clipboard } from './clipboard.svg'; +export { default as Billing } from './billing.svg'; +export { default as Caret } from './caret.svg'; +export { default as ChatAltIcon } from './chat-alt.svg'; export { default as CheckCircle } from './check-circle.svg'; -export { default as ExclamationTriangle } from './exclamation-triangle.svg'; +export { default as Clipboard } from './clipboard.svg'; +export { default as Close } from './close.svg'; +export { default as CogFilled } from './cog-filled.svg'; +export { default as DeviceLaptop } from './device-laptop.svg'; +export { default as DeviceMobile } from './device-mobile.svg'; +export { default as DotCircle } from './dot-circle-horizontal.svg'; +export { default as Email } from './email.svg'; export { default as ExclamationCircle } from './exclamation-circle.svg'; +export { default as ExclamationTriangle } from './exclamation-triangle.svg'; export { default as EyeSlash } from './eye-slash.svg'; +export { default as Eye } from './eye.svg'; +export { default as Folder } from './folder.svg'; +export { default as Image } from './image.svg'; export { default as InformationCircle } from './information-circle.svg'; +export { default as LinkIcon } from './link.svg'; +export { default as LockClosedIcon } from './lock-closed.svg'; +export { default as LogoMark } from './logo-mark-new.svg'; export { default as MagnifyingGlass } from './magnifying-glass.svg'; -export { default as Caret } from './caret.svg'; +export { default as Menu } from './menu.svg'; +export { default as Mobile } from './mobile-small.svg'; export { default as PencilEdit } from './pencil-edit.svg'; export { default as Pencil } from './pencil.svg'; -export { default as Email } from './email.svg'; -export { default as TickShield } from './tick-shield.svg'; -export { default as SwitchArrows } from './switch-arrows.svg'; -export { default as LogoMark } from './logo-mark-new.svg'; -export { default as SignOut } from './signout.svg'; -export { default as SignOutDouble } from './signout-double.svg'; export { default as Plus } from './plus.svg'; -export { default as CogFilled } from './cog-filled.svg'; -export { default as LinkIcon } from './link.svg'; -export { default as LockClosedIcon } from './lock-closed.svg'; -export { default as ChatAltIcon } from './chat-alt.svg'; +export { default as QuestionMark } from './question-mark.svg'; export { default as RequestAuthIcon } from './request-auth.svg'; -export { default as User } from './user.svg'; -export { default as Mobile } from './mobile-small.svg'; -export { default as Folder } from './folder.svg'; -export { default as DeviceMobile } from './device-mobile.svg'; -export { default as DeviceLaptop } from './device-laptop.svg'; -export { default as Menu } from './menu.svg'; -export { default as Eye } from './eye.svg'; -export { default as DotCircle } from './dot-circle-horizontal.svg'; export { default as Selector } from './selector.svg'; +export { default as SignOutDouble } from './signout-double.svg'; +export { default as SignOut } from './signout.svg'; +export { default as SwitchArrows } from './switch-arrows.svg'; +export { default as ThreeDots } from './threeDots.svg'; +export { default as TickShield } from './tick-shield.svg'; export { default as Times } from './times.svg'; export { default as Trash } from './trash.svg'; +export { default as Upload } from './upload.svg'; +export { default as User } from './user.svg'; export { default as UserAdd } from './userAdd.svg'; -export { default as ThreeDots } from './threeDots.svg'; -export { default as Billing } from './billing.svg'; -export { default as Close } from './close.svg'; -export { default as QuestionMark } from './question-mark.svg'; diff --git a/packages/clerk-js/src/ui/icons/upload.svg b/packages/clerk-js/src/ui/icons/upload.svg new file mode 100644 index 00000000000..404b0988bdf --- /dev/null +++ b/packages/clerk-js/src/ui/icons/upload.svg @@ -0,0 +1 @@ + \ No newline at end of file