9c4b0f526c
## Summary Cleans up the chip component hierarchy in `twenty-ui`: - **Fix twenty-ui Storybook** — The `wyw-in-js` Vite plugin crashed on `/@react-refresh` virtual module. Fixed by setting `enforce: 'pre'` so it runs before the React refresh plugin injects virtual imports. - **Rename `AvatarChip` → `AvatarOrIcon`** — The old name was misleading. This component is not a chip — it's a polymorphic renderer that displays either an `Avatar` (image/initials) or an `Icon` (plain or with colored background). It's typically slotted into `Chip`/`LinkChip` as `leftComponent`. - **Move `rightComponentDivider` to `Chip`/`LinkChip`** — The vertical separator between chip content and a right action (e.g. a close button) is a chip layout concern, not an avatar concern. Added `rightComponentDivider` boolean prop to `Chip` and `LinkChip`. - **Remove `MultipleAvatarChip`** — Zero consumers in the codebase. The command menu implements its own overlapping avatar layout. - **Migrate raw icon usages** — `CalendarEventDetails` and `FileIcon` (small size) now use `AvatarOrIcon` for consistent Chip icon rendering. - **Enhance stories** — Full `CatalogDecorator` coverage for `Chip` and `LinkChip` showing all variants, sizes, accents, and states. ## Component hierarchy ``` AvatarOrIcon (twenty-ui) ├── No Icon → renders Avatar (image or initials) ├── Icon + background → renders icon in colored square └── Icon only → renders plain icon Used as leftComponent/rightComponent in Chip or standalone Chip (twenty-ui) ├── leftComponent (typically AvatarOrIcon) ├── label (with overflow tooltip) ├── rightComponentDivider (optional vertical separator) └── rightComponent (e.g. close icon via AvatarOrIcon) LinkChip (twenty-ui) └── Wraps Chip inside a react-router <Link> RecordChip (twenty-front) └── Composes Chip/LinkChip + AvatarOrIcon with record data ``` ## `Chip` API additions | Prop | Type | Description | |------|------|-------------| | `rightComponentDivider` | `boolean` | Renders a vertical separator before `rightComponent` | ## Stories <img width="1032" height="576" alt="image" src="https://github.com/user-attachments/assets/fe7c7666-9b16-4545-b87e-1b53e22d462d" />
87 lines
2.3 KiB
TypeScript
87 lines
2.3 KiB
TypeScript
import { type AttachmentFileCategory } from '@/activities/files/types/AttachmentFileCategory';
|
|
import { getFileType } from '@/activities/files/utils/getFileType';
|
|
import { useFileCategoryColors } from '@/file/hooks/useFileCategoryColors';
|
|
import { IconMapping } from '@/file/utils/fileIconMappings';
|
|
import { useTheme } from '@emotion/react';
|
|
import { t } from '@lingui/core/macro';
|
|
import { type FileUIPart } from 'ai';
|
|
import { isDefined } from 'twenty-shared/utils';
|
|
import {
|
|
AvatarOrIcon,
|
|
Chip,
|
|
ChipVariant,
|
|
LinkChip,
|
|
} from 'twenty-ui/components';
|
|
import { type IconComponent, IconX } from 'twenty-ui/display';
|
|
import { Loader } from 'twenty-ui/feedback';
|
|
|
|
export const AgentChatFilePreview = ({
|
|
file,
|
|
onRemove,
|
|
isUploading,
|
|
}: {
|
|
file: FileUIPart | File;
|
|
onRemove?: () => void;
|
|
isUploading?: boolean;
|
|
}) => {
|
|
const theme = useTheme();
|
|
const iconColors: Record<AttachmentFileCategory, string> =
|
|
useFileCategoryColors();
|
|
|
|
const fileName =
|
|
file instanceof File ? file.name : (file.filename ?? t`Unknown file`);
|
|
|
|
const fileUrl = file instanceof File ? undefined : file.url;
|
|
|
|
const fileCategory: AttachmentFileCategory = getFileType(fileName);
|
|
|
|
const FileCategoryIcon: IconComponent = IconMapping[fileCategory];
|
|
const iconBackgroundColor: string = iconColors[fileCategory];
|
|
|
|
const leftComponent = isUploading ? (
|
|
<Loader color="yellow" />
|
|
) : (
|
|
<AvatarOrIcon
|
|
Icon={FileCategoryIcon}
|
|
IconBackgroundColor={iconBackgroundColor}
|
|
/>
|
|
);
|
|
|
|
const rightComponent = onRemove ? (
|
|
<AvatarOrIcon
|
|
Icon={IconX}
|
|
IconColor={theme.font.color.secondary}
|
|
onClick={onRemove}
|
|
/>
|
|
) : undefined;
|
|
|
|
const hasRightDivider = isDefined(onRemove);
|
|
|
|
if (isDefined(fileUrl)) {
|
|
return (
|
|
<LinkChip
|
|
label={fileName}
|
|
emptyLabel={t`Untitled`}
|
|
variant={ChipVariant.Static}
|
|
to={fileUrl}
|
|
target="_blank"
|
|
leftComponent={leftComponent}
|
|
rightComponent={rightComponent}
|
|
rightComponentDivider={hasRightDivider}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Chip
|
|
label={fileName}
|
|
emptyLabel={t`Untitled`}
|
|
variant={ChipVariant.Static}
|
|
clickable={false}
|
|
leftComponent={leftComponent}
|
|
rightComponent={rightComponent}
|
|
rightComponentDivider={hasRightDivider}
|
|
/>
|
|
);
|
|
};
|