Cal.comdesign-system

Image & Banner Uploaders

Dialog-based image upload with crop for avatars/logos and org banners.

Uploader family for profile avatars, team/org logos, and organization banners. Both variants share the same dialog + file-pick + crop pipeline under packages/features/components/image-uploader. For an AI image that is still generating, use AI image placeholder instead of an uploader empty state.

Ownership

Feature โ€” not @coss/ui/shared. Crop pipeline (react-easy-crop), i18n toasts/labels, 5MB file limit, and product size contracts (1:1 avatar vs 3:1 banner at caller height/width) keep these Feature-owned. Document both variants together; do not relocate for docs.

ImageUploader

Square avatar/logo upload. Production crops 1:1 in a circular cropper with zoom slider, then returns a data URL via handleAvatarChange.

Use when

  • Uploading a profile avatar or team/organization logo.
  • The caller needs Cal's file-size validation, circular crop, zoom, and encoded data URL contract.
  • The surrounding surface owns preview composition, persistence, and the remove action.

Do not import react-easy-crop or recreate the dialog/canvas pipeline in a caller. Import ImageUploader and compose it with the appropriate preview.

Canonical profile composition

The profile page shows UserAvatar initials when no image is set, overlays an organization logo when the user belongs to one, and only renders Remove after the form value contains an image.

apps/web/modules/settings/my-account/components/profile-form-card.tsx

API

BannerUploader

Wide banner upload for organizations. Production crops 3:1 and encodes to the callerโ€™s width ร— height (typically 1500 ร— 500).

Use when

  • Uploading an organization banner with the standard 3:1 crop contract.
  • The caller can supply explicit output width and height.
  • The surrounding surface owns the empty state, preview, persistence, and remove action.

Canonical organization composition

The organization profile mounts a hidden uploader inside the empty OrgBanner fallback so the visible in-plane button opens the standard dialog. Once a banner exists, the fallback unmounts and the uploader plus remove action render below the image.

apps/web/modules/ee/organizations/components/org-profile-form-card.tsx

API

Same trigger/dialog props as ImageUploader, plus:

Call sites

Example URLs

Not exhaustive โ€” a few places you can open locally (e.g. http://localhost:3000) to see this in product.

Source