blob: 9984931172d16a20784abfe7815c3e69d4af6264 [file]
import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import '../../../utils/styles.dart';
import 'icons.dart';
/// A reusable image thumbnail component used for displaying image attachments.
///
/// Supports an optional remove button and click-to-preview behavior.
/// Used in both the chat composer (for pending uploads) and message bubbles
/// (for sent image attachments).
class ImageThumbnail extends StatelessComponent {
const ImageThumbnail({
required this.imageUrl,
required this.fileName,
this.onRemove,
this.onTap,
super.key,
});
/// The data URL or source URL of the image to display.
final String imageUrl;
/// The alt text / file name for accessibility.
final String fileName;
/// Optional callback when the remove button is clicked.
/// When null, the remove button is hidden.
final VoidCallback? onRemove;
/// Optional callback when the thumbnail is clicked (excluding the remove button).
final VoidCallback? onTap;
@override
Component build(BuildContext context) {
return div(
classes: 'image-thumbnail',
events: {
if (onTap != null)
'click': (_) {
onTap!();
},
},
[
img(
classes: 'image-thumbnail-img',
src: imageUrl,
attributes: {'alt': fileName},
),
if (onRemove != null)
button(
classes: 'image-thumbnail-remove',
attributes: {'type': 'button', 'aria-label': 'Remove $fileName'},
events: {
'click': (e) {
e.stopPropagation();
onRemove!();
},
},
[
closeIcon(size: 10, color: 'currentColor'),
],
),
],
);
}
@css
static List<StyleRule> get styles => [
// Thumbnail container
css('.image-thumbnail').styles(
display: .flex,
position: const Position.relative(),
width: 56.px,
height: 56.px,
border: Border.all(color: colorBorder, width: 1.px),
radius: .circular(6.px),
overflow: .hidden,
cursor: .pointer,
transition: Transition('border-color', duration: 150.ms),
raw: {'flex-shrink': '0'},
),
css('.image-thumbnail:hover').styles(
border: Border.all(color: colorPrimary.withOpacity(0.6), width: 1.px),
),
// Image fill
css('.image-thumbnail-img').styles(
width: 100.percent,
height: 100.percent,
raw: {'object-fit': 'cover'},
),
// Remove button (overlay)
css('.image-thumbnail-remove').styles(
display: .flex,
position: .absolute(top: 2.px, right: 2.px),
width: 18.px,
height: 18.px,
padding: Padding.zero,
border: Border.none,
radius: .circular(9.px),
cursor: .pointer,
transition: Transition('all', duration: 150.ms),
justifyContent: .center,
alignItems: .center,
color: colorOnSurface,
backgroundColor: colorSurface.withOpacity(0.85),
raw: {'backdrop-filter': 'blur(4px)'},
),
css('.image-thumbnail-remove:hover').styles(
color: colorError,
backgroundColor: colorError.withOpacity(0.2),
),
];
}