/* * 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 ; } }