diff --git a/images/caption-shadow.svg b/images/caption-shadow.svg
index 42ab5a93a2..444ae5b9a7 100644
--- a/images/caption-shadow.svg
+++ b/images/caption-shadow.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/chat-session-refresh.svg b/images/chat-session-refresh.svg
index 812fce788f..64c39bd701 100644
--- a/images/chat-session-refresh.svg
+++ b/images/chat-session-refresh.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/check-circle-outline.svg b/images/check-circle-outline.svg
index 2c03039440..c256ebbb8c 100644
--- a/images/check-circle-outline.svg
+++ b/images/check-circle-outline.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/delivery-issue.svg b/images/delivery-issue.svg
index 64edbf0904..2f3b730a33 100644
--- a/images/delivery-issue.svg
+++ b/images/delivery-issue.svg
@@ -1,13 +1 @@
-
+
\ No newline at end of file
diff --git a/images/double-check.svg b/images/double-check.svg
index 79b9fcdbd1..fec1f9dae5 100644
--- a/images/double-check.svg
+++ b/images/double-check.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/file.svg b/images/file.svg
index dedeef231a..5169740223 100644
--- a/images/file.svg
+++ b/images/file.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/generic-file-dangerous.svg b/images/generic-file-dangerous.svg
new file mode 100644
index 0000000000..026df2e326
--- /dev/null
+++ b/images/generic-file-dangerous.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/images/generic-file.svg b/images/generic-file.svg
index e01844db9c..0d4e35abc3 100644
--- a/images/generic-file.svg
+++ b/images/generic-file.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/gift-bow.svg b/images/gift-bow.svg
index 2798d1bfc4..1f4eb1d639 100644
--- a/images/gift-bow.svg
+++ b/images/gift-bow.svg
@@ -1,16 +1 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/gift-thumbnail.svg b/images/gift-thumbnail.svg
index 2199099269..00fa772fe4 100644
--- a/images/gift-thumbnail.svg
+++ b/images/gift-thumbnail.svg
@@ -1,19 +1 @@
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/image.svg b/images/image.svg
index 21ba4507b9..c274341862 100644
--- a/images/image.svg
+++ b/images/image.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/local-delete-sync.svg b/images/local-delete-sync.svg
index dee360ed29..53e332d932 100644
--- a/images/local-delete-sync.svg
+++ b/images/local-delete-sync.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/macos-switch.svg b/images/macos-switch.svg
index 6393b5ff60..ec589b2d17 100644
--- a/images/macos-switch.svg
+++ b/images/macos-switch.svg
@@ -1,17 +1 @@
-
+
\ No newline at end of file
diff --git a/images/merged-chat.svg b/images/merged-chat.svg
index 06dab79558..26f5a5bb7f 100644
--- a/images/merged-chat.svg
+++ b/images/merged-chat.svg
@@ -1,12 +1 @@
-
+
\ No newline at end of file
diff --git a/images/mobile-settings-dark.svg b/images/mobile-settings-dark.svg
index 20c58c0418..3eb1baedc7 100644
--- a/images/mobile-settings-dark.svg
+++ b/images/mobile-settings-dark.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/mobile-settings-light.svg b/images/mobile-settings-light.svg
index 76ff934d06..a2555ecf96 100644
--- a/images/mobile-settings-light.svg
+++ b/images/mobile-settings-light.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/movie.svg b/images/movie.svg
index 0cec0e7bb5..8e9b729747 100644
--- a/images/movie.svg
+++ b/images/movie.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/phone_40_color.svg b/images/phone_40_color.svg
index 9e2700e2ac..cbd1415eff 100644
--- a/images/phone_40_color.svg
+++ b/images/phone_40_color.svg
@@ -1,7 +1 @@
-
+
\ No newline at end of file
diff --git a/images/phone_40_color_dark.svg b/images/phone_40_color_dark.svg
index 30f3a3f6bc..c62e5430d6 100644
--- a/images/phone_40_color_dark.svg
+++ b/images/phone_40_color_dark.svg
@@ -1,17 +1 @@
-
+
\ No newline at end of file
diff --git a/images/profile-avatar.svg b/images/profile-avatar.svg
index 50772c4fe0..01654d4883 100644
--- a/images/profile-avatar.svg
+++ b/images/profile-avatar.svg
@@ -1,19 +1 @@
-
+
\ No newline at end of file
diff --git a/images/profile-moved-dark.svg b/images/profile-moved-dark.svg
index bbe3474438..4adc6a85c1 100644
--- a/images/profile-moved-dark.svg
+++ b/images/profile-moved-dark.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/profile-moved.svg b/images/profile-moved.svg
index 3c1feede3e..39287d69fc 100644
--- a/images/profile-moved.svg
+++ b/images/profile-moved.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/qr_codes_40_color.svg b/images/qr_codes_40_color.svg
index a660b384b5..531f699ddc 100644
--- a/images/qr_codes_40_color.svg
+++ b/images/qr_codes_40_color.svg
@@ -1,32 +1 @@
-
+
\ No newline at end of file
diff --git a/images/qr_codes_40_color_dark.svg b/images/qr_codes_40_color_dark.svg
index 1cad44a0b5..4fd3d08cc8 100644
--- a/images/qr_codes_40_color_dark.svg
+++ b/images/qr_codes_40_color_dark.svg
@@ -1,30 +1 @@
-
+
\ No newline at end of file
diff --git a/images/read.svg b/images/read.svg
index 1083ee9cd8..152a772242 100644
--- a/images/read.svg
+++ b/images/read.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/rocket-160.svg b/images/rocket-160.svg
index 4dc3f96671..275781af2d 100644
--- a/images/rocket-160.svg
+++ b/images/rocket-160.svg
@@ -1,41 +1 @@
-
+
\ No newline at end of file
diff --git a/images/rocket-36-dark.svg b/images/rocket-36-dark.svg
index 5ec0c73975..c5f1325bd4 100644
--- a/images/rocket-36-dark.svg
+++ b/images/rocket-36-dark.svg
@@ -1,31 +1 @@
-
+
\ No newline at end of file
diff --git a/images/rocket-36-light.svg b/images/rocket-36-light.svg
index b946f9513e..ab13c60bb7 100644
--- a/images/rocket-36-light.svg
+++ b/images/rocket-36-light.svg
@@ -1,35 +1 @@
-
+
\ No newline at end of file
diff --git a/images/safety-number-migration.svg b/images/safety-number-migration.svg
index 75d875b29a..f6299bb22e 100644
--- a/images/safety-number-migration.svg
+++ b/images/safety-number-migration.svg
@@ -1,42 +1 @@
-
+
\ No newline at end of file
diff --git a/images/sending.svg b/images/sending.svg
index 5f4c58e59c..05e776fb25 100644
--- a/images/sending.svg
+++ b/images/sending.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/signal-logo-and-wordmark.svg b/images/signal-logo-and-wordmark.svg
index e90dedda61..46ed3ad81b 100644
--- a/images/signal-logo-and-wordmark.svg
+++ b/images/signal-logo-and-wordmark.svg
@@ -1,26 +1 @@
-
+
\ No newline at end of file
diff --git a/images/signal-logo.svg b/images/signal-logo.svg
index 036d0696f6..61f00c2066 100644
--- a/images/signal-logo.svg
+++ b/images/signal-logo.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/spinner-24.svg b/images/spinner-24.svg
index 3544360267..fca8aa872e 100644
--- a/images/spinner-24.svg
+++ b/images/spinner-24.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/spinner-56.svg b/images/spinner-56.svg
index d5a0e60ca4..3fec7503fd 100644
--- a/images/spinner-56.svg
+++ b/images/spinner-56.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/spinner-track-24.svg b/images/spinner-track-24.svg
index a209386503..b2ba900da9 100644
--- a/images/spinner-track-24.svg
+++ b/images/spinner-track-24.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/spinner-track-56.svg b/images/spinner-track-56.svg
index 631863d731..e31eea4a96 100644
--- a/images/spinner-track-56.svg
+++ b/images/spinner-track-56.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/titlebar_icon.svg b/images/titlebar_icon.svg
index 012b952653..eb5c1793ec 100644
--- a/images/titlebar_icon.svg
+++ b/images/titlebar_icon.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/usernames_40_color.svg b/images/usernames_40_color.svg
index 710e7915d2..b69d7b0a27 100644
--- a/images/usernames_40_color.svg
+++ b/images/usernames_40_color.svg
@@ -1,5 +1 @@
-
+
\ No newline at end of file
diff --git a/images/usernames_40_color_dark.svg b/images/usernames_40_color_dark.svg
index 842d05b0cc..999459a73a 100644
--- a/images/usernames_40_color_dark.svg
+++ b/images/usernames_40_color_dark.svg
@@ -1,4 +1 @@
-
+
\ No newline at end of file
diff --git a/images/x-shadow-16.svg b/images/x-shadow-16.svg
index 88a5250760..f778587cb6 100644
--- a/images/x-shadow-16.svg
+++ b/images/x-shadow-16.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/package.json b/package.json
index c2a5b43e97..ece1a5359b 100644
--- a/package.json
+++ b/package.json
@@ -63,7 +63,7 @@
"danger:local": "./danger/danger.sh local --base main",
"danger:ci": "./danger/danger.sh ci --base origin/main",
"format": "pprettier --write '**/*.{ts,tsx,d.ts,js,json,html,scss,md,yml,yaml}' '!node_modules/**'",
- "svgo": "svgo --multipass images/**/*.svg",
+ "svgo": "svgo --multipass images/**/*.svg images/*.svg",
"transpile": "run-p check:types build:esbuild",
"check:types": "tsc --noEmit",
"clean-transpile": "node ./scripts/clean-transpile.js",
diff --git a/stylesheets/_modules.scss b/stylesheets/_modules.scss
index 35121977b8..5f2f9bbeba 100644
--- a/stylesheets/_modules.scss
+++ b/stylesheets/_modules.scss
@@ -711,42 +711,6 @@ $message-padding-horizontal: 12px;
padding-top: 4px;
}
-.module-message__simple-attachment__icon {
- background: url('../images/generic-file.svg') no-repeat center;
- height: 40px;
- width: 30px;
- margin-inline-start: 3px;
- margin-inline-end: 3px;
-
- // So we can center the extension text inside this icon
- display: flex;
- flex-direction: row;
- align-items: center;
-}
-
-.module-message__simple-attachment__icon-dangerous-container {
- position: absolute;
-
- top: -1px;
- inset-inline-end: -4px;
-
- height: 16px;
- width: 16px;
-
- border-radius: 50%;
- background-color: variables.$color-white;
-}
-
-.module-message__simple-attachment__icon-dangerous {
- height: 16px;
- width: 16px;
-
- @include mixins.color-svg(
- '../images/icons/v2/error-solid-24.svg',
- variables.$color-accent-red
- );
-}
-
.module-message__simple-attachment__icon__extension {
@include mixins.font-subtitle;
text-transform: lowercase;
@@ -3345,11 +3309,17 @@ button.module-image__border-overlay:focus {
// Module: Staged Generic Attachment
.module-staged-generic-attachment {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 5px;
+
height: 120px;
width: 120px;
- display: inline-block;
position: relative;
border-radius: 4px;
+ padding: 7px;
vertical-align: middle;
white-space: nowrap;
@@ -3365,6 +3335,10 @@ button.module-image__border-overlay:focus {
}
.module-staged-generic-attachment__close-button {
+ position: absolute;
+ inset-inline-end: 5px;
+ top: 0;
+
@include mixins.staged-attachment-close-button;
& {
@@ -3383,49 +3357,13 @@ button.module-image__border-overlay:focus {
}
}
-.module-staged-generic-attachment__icon {
- margin-top: 30px;
-
- background: url('../images/generic-file.svg') no-repeat center;
- height: 44px;
- width: 56px;
- margin-inline: 32px;
- margin-bottom: -4px;
-
- // So we can center the extension text inside this icon
- display: flex;
- flex-direction: row;
- align-items: center;
-}
-
-.module-staged-generic-attachment__icon__extension {
- font-size: 10px;
- line-height: 13px;
- letter-spacing: 0.1px;
- text-transform: uppercase;
-
- // Along with flow layout in parent item, centers text
- text-align: center;
- width: 25px;
- margin-inline: auto;
-
- // We don't have much room for text here, cut it off without ellipse
- overflow-x: hidden;
- white-space: nowrap;
- text-overflow: clip;
-
- color: variables.$color-gray-90;
-}
-
.module-staged-generic-attachment__filename {
@include mixins.font-caption;
- margin: 7px;
- margin-top: 5px;
+ max-width: 100%;
text-align: center;
overflow: hidden;
- height: 2.4em;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
diff --git a/ts/components/FileThumbnail.stories.tsx b/ts/components/FileThumbnail.stories.tsx
new file mode 100644
index 0000000000..462eba621b
--- /dev/null
+++ b/ts/components/FileThumbnail.stories.tsx
@@ -0,0 +1,42 @@
+// Copyright 2025 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import React from 'react';
+import type { Meta } from '@storybook/react';
+import { FileThumbnail } from './FileThumbnail';
+import { APPLICATION_OCTET_STREAM } from '../types/MIME';
+
+export default {
+ title: 'FileThumbnail',
+} satisfies Meta;
+
+export function ThreeLetterExtension(): JSX.Element {
+ return (
+
+ );
+}
+
+export function FourLetterExtension(): JSX.Element {
+ return (
+
+ );
+}
+
+export function ManyLetterExtension(): JSX.Element {
+ return (
+
+ );
+}
+
+export function OnlyContentType(): JSX.Element {
+ return ;
+}
+
+export function DangerousExtension(): JSX.Element {
+ return (
+
+ );
+}
diff --git a/ts/components/FileThumbnail.tsx b/ts/components/FileThumbnail.tsx
new file mode 100644
index 0000000000..b2fec1f097
--- /dev/null
+++ b/ts/components/FileThumbnail.tsx
@@ -0,0 +1,49 @@
+// Copyright 2025 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import React from 'react';
+
+import { getExtensionForDisplay } from '../types/Attachment';
+import { isFileDangerous } from '../util/isFileDangerous';
+import { tw } from '../axo/tw';
+
+export type PropsType = Readonly[0]>;
+
+export function FileThumbnail(props: PropsType): JSX.Element {
+ const extension = getExtensionForDisplay(props) ?? '';
+ const isDangerous = isFileDangerous(props.fileName || '');
+
+ return (
+