/*
* Copyright 2026 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import AlertTriangle from '@react-spectrum/s2/icons/AlertTriangle';
import {
AriaLabelingProps,
DOMProps,
DOMRef,
forwardRefType,
GlobalDOMAttributes
} from '@react-types/shared';
import {
baseColor,
focusRing,
iconStyle,
style
} from '@react-spectrum/s2/style' with {type: 'macro'};
import {BasicHorizontalCard} from './HorizontalCard';
import {Button} from 'react-aria-components/Button';
import {CardProps} from '@react-spectrum/s2/Card';
import Cross from '../ui-icons/Cross';
import {forwardRef, ReactNode, useRef} from 'react';
import {ImageContext} from '@react-spectrum/s2/Image';
// @ts-ignore
import intlMessages from '../intl/*.json';
import {mergeStyles} from '@react-spectrum/s2/mergeStyles';
import {pressScale} from '@react-spectrum/s2/pressScale';
import {ProgressCircle} from '@react-spectrum/s2/ProgressCircle';
import {StyleString} from '@react-spectrum/s2/style' with {type: 'macro'};
import {
Tag,
TagGroup,
TagGroupProps,
TagList,
TagListProps,
TagProps
} from 'react-aria-components/TagGroup';
import {useDOMRef} from './useDOMRef';
import {useLocalizedStringFormatter} from 'react-aria/useLocalizedStringFormatter';
interface AttachmentRenderProps {
/** The size of the Card. */
size: 'XS' | 'S' | 'M' | 'L' | 'XL';
}
const controlSizeM = {
default: 32,
size: {
XS: 20,
S: 24,
L: 40,
XL: 48
}
} as const;
const hoverBackground = {
default: 'gray-200',
isStaticColor: 'transparent-overlay-200'
} as const;
const styles = style<{
isDisabled: boolean;
isHovered: boolean;
isFocusVisible: boolean;
isPressed: boolean;
size: 'S' | 'M' | 'L' | 'XL';
}>({
...focusRing(),
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
size: controlSizeM,
flexShrink: 0,
borderRadius: 'full',
padding: 0,
borderStyle: 'none',
transition: 'default',
backgroundColor: {
default: 'gray-200',
isHovered: hoverBackground,
isFocusVisible: hoverBackground,
isPressed: hoverBackground
},
color: {
default: baseColor('neutral'),
isDisabled: 'disabled',
forcedColors: {
default: 'ButtonText',
isDisabled: 'GrayText'
}
},
'--iconPrimary': {
type: 'fill',
value: 'currentColor'
},
outlineColor: {
default: 'focus-ring',
forcedColors: 'Highlight'
},
disableTapHighlight: true
});
const CloseButton = function CloseButton(props) {
let ref = useRef(null);
// oxlint-disable react/react-compiler
return (
);
// oxlint-enable react/react-compiler
};
export interface AttachmentListProps
extends
DOMProps,
Omit<
TagGroupProps,
| 'children'
| 'selectionMode'
| 'defaultSelectedKeys'
| 'selectionBehavior'
| 'selectedKeys'
| 'disallowEmptySelection'
| 'escapeKeyBehavior'
| 'onSelectionChange'
| 'shouldSelectOnPressUp'
| 'onAction'
| 'render'
| 'style'
| 'className'
| keyof GlobalDOMAttributes
>,
Pick, 'items' | 'children' | 'dependencies'> {
/**
* Spectrum-defined styles, returned by the `style()` macro.
*/
styles?: StyleString;
}
export const AttachmentList = (forwardRef as forwardRefType)(function AttachmentList(
props: AttachmentListProps,
ref: DOMRef
) {
let {styles, items, children, dependencies, ...otherProps} = props;
let domRef = useDOMRef(ref);
return (
{children}
);
});
export interface AttachmentProps
extends
Omit<
CardProps,
'styles' | 'UNSAFE_className' | 'UNSAFE_style' | 'allowsArrowNavigation' | 'focusMode'
>,
AriaLabelingProps,
Pick {
/** The children of the Attachment. */
children: ReactNode | ((renderProps: AttachmentRenderProps) => ReactNode);
uploadProgress?: number;
/** Whether the attachment has an error. */
isInvalid?: boolean;
/**
* Spectrum-defined styles, returned by the `style()` macro.
*/
styles?: StyleString;
}
const tagStyles = style({
flexShrink: 0,
flexGrow: 0,
position: 'relative',
...focusRing(),
borderRadius: 'default'
});
const attachmentErrorStyles = style({
display: 'flex',
flexShrink: 0,
alignItems: 'center',
paddingStart: 8,
'--iconPrimary': {
type: 'color',
value: 'negative'
}
});
export const Attachment = forwardRef(function Attachment(
props: AttachmentProps,
ref: DOMRef
) {
let {
id,
textValue,
'aria-label': ariaLabel,
'aria-labelledby': ariaLabelledby,
'aria-describedby': ariaDescribedby,
styles,
isInvalid,
children,
size = 'M',
...otherProps
} = props;
let domRef = useDOMRef(ref);
let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/ai');
return (
mergeStyles(tagStyles({...renderProps}), styles)}>
{props.uploadProgress != null && props.uploadProgress < 100 && (
)}
{/* Reduce opacity of the thumbnail if upload is in progress */}
{ctx => (
{typeof children === 'function' ? children({size}) : children}
)}
{isInvalid && (
)}
{/** Definitely not a close button, though looks like one. */}
);
});
function AlertTriangleIcon({size}) {
switch (size) {
case 'XS':
return ;
case 'S':
return ;
case 'M':
return ;
case 'L':
return ;
case 'XL':
return ;
}
}