Decouple twenty-ui Avatar from app server-URL config (#21968)

Makes `twenty-ui`'s `Avatar` render the `avatarUrl` it receives instead
of building it from `window._env_`/`window.location` at module load, so
the library no longer depends on the app environment. URL resolution
moves to `twenty-front` via a `getAbsoluteImageUrl` helper applied at
the call sites.

Part of making twenty-ui a standalone library.
This commit is contained in:
Raphaël Bosi
2026-06-22 18:38:00 +02:00
committed by GitHub
parent e59e102448
commit 5f22908588
47 changed files with 198 additions and 115 deletions
@@ -7,6 +7,7 @@ import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { canOpenObjectInSidePanel } from '@/object-record/utils/canOpenObjectInSidePanel';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { ViewOpenRecordIn } from '~/generated-metadata/graphql';
import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl';
import { t } from '@lingui/core/macro';
import { type MouseEvent } from 'react';
import { isDefined } from 'twenty-shared/utils';
@@ -98,7 +99,7 @@ export const RecordChip = ({
placeholder={recordChipData.name}
placeholderColorSeed={record.id}
avatarType={recordChipData.avatarType}
avatarUrl={recordChipData.avatarUrl ?? ''}
avatarUrl={getAbsoluteImageUrl(recordChipData.avatarUrl ?? '')}
/>
)
}
@@ -120,7 +121,7 @@ export const RecordChip = ({
placeholder={recordChipData.name}
placeholderColorSeed={record.id}
avatarType={recordChipData.avatarType}
avatarUrl={recordChipData.avatarUrl ?? ''}
avatarUrl={getAbsoluteImageUrl(recordChipData.avatarUrl ?? '')}
/>
)
}
@@ -3,6 +3,7 @@ import { styled } from '@linaria/react';
import { Avatar } from 'twenty-ui/data-display';
import { MenuItemMultiSelectAvatar } from 'twenty-ui/navigation';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl';
const StyledPinnedItemsContainer = styled.div`
display: flex;
@@ -31,7 +32,7 @@ export const ObjectFilterDropdownRecordPinnedItems = (props: {
avatar={
selectableItem.avatarUrl ? (
<Avatar
avatarUrl={selectableItem.avatarUrl}
avatarUrl={getAbsoluteImageUrl(selectableItem.avatarUrl)}
placeholderColorSeed={selectableItem.id}
placeholder={selectableItem.name}
type={selectableItem.avatarType}
@@ -18,6 +18,7 @@ import { MenuItemMultiSelectAvatar } from 'twenty-ui/navigation';
import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState';
import { type SearchRecord } from '~/generated/graphql';
import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl';
type MultipleRecordPickerMenuItemContentProps = {
searchRecord: SearchRecord;
@@ -83,7 +84,7 @@ export const MultipleRecordPickerMenuItemContent = ({
selected={isRecordSelectedWithObjectItem}
avatar={
<Avatar
avatarUrl={searchRecord.imageUrl}
avatarUrl={getAbsoluteImageUrl(searchRecord.imageUrl)}
placeholderColorSeed={searchRecord.recordId}
placeholder={displayText}
size="md"
@@ -10,6 +10,7 @@ import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/com
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useAtomComponentFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentFamilyStateValue';
import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { Avatar } from 'twenty-ui/data-display';
import { MenuItemSelectAvatar } from 'twenty-ui/navigation';
@@ -73,7 +74,7 @@ export const SingleRecordPickerMenuItem = ({
focused={isSelectedItemId}
avatar={
<Avatar
avatarUrl={searchRecordStore.imageUrl}
avatarUrl={getAbsoluteImageUrl(searchRecordStore.imageUrl)}
placeholderColorSeed={morphItem.recordId}
placeholder={searchRecordStore.label}
size="md"
@@ -10,6 +10,7 @@ import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectab
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { getAbsoluteImageUrl } from '~/utils/image/getAbsoluteImageUrl';
import { t } from '@lingui/core/macro';
import { Avatar } from 'twenty-ui/data-display';
import { MenuItem, MenuItemMultiSelectAvatar } from 'twenty-ui/navigation';
@@ -107,7 +108,7 @@ export const MultipleSelectDropdown = ({
text={item.name}
avatar={
<Avatar
avatarUrl={item.avatarUrl}
avatarUrl={getAbsoluteImageUrl(item.avatarUrl)}
placeholderColorSeed={item.id}
placeholder={item.name}
size="md"