diff --git a/fluxer_app/src/features/app/components/layout/GuildsLayout.module.css b/fluxer_app/src/features/app/components/layout/GuildsLayout.module.css
index bcc1862c6..f4ae1fdc4 100644
--- a/fluxer_app/src/features/app/components/layout/GuildsLayout.module.css
+++ b/fluxer_app/src/features/app/components/layout/GuildsLayout.module.css
@@ -306,14 +306,9 @@
isolation: isolate;
}
-.guildBadgeActive::before,
-.guildBadge:not(:empty)::before {
- content: '';
- position: absolute;
- inset: -0.1875rem;
- z-index: -1;
- border-radius: calc(var(--radius-md) + 0.1875rem);
- background-color: var(--guild-badge-surface);
+.guildBadgeActive,
+.guildBadge:not(:empty) {
+ --mention-badge-cutout-surface: var(--guild-badge-surface);
}
.guildVoiceBadge {
diff --git a/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildFolderItem.module.css b/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildFolderItem.module.css
index cadeb037b..958cee0c4 100644
--- a/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildFolderItem.module.css
+++ b/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildFolderItem.module.css
@@ -145,14 +145,9 @@
isolation: isolate;
}
-.folderBadgeActive::before,
-.folderBadge:not(:empty)::before {
- content: '';
- position: absolute;
- inset: -0.1875rem;
- z-index: -1;
- border-radius: calc(var(--radius-md) + 0.1875rem);
- background-color: var(--guild-badge-surface);
+.folderBadgeActive,
+.folderBadge:not(:empty) {
+ --mention-badge-cutout-surface: var(--guild-badge-surface);
}
.expandedFolderBackground {
diff --git a/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildFolderItem.tsx b/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildFolderItem.tsx
index 337858ec3..139fdb93b 100644
--- a/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildFolderItem.tsx
+++ b/fluxer_app/src/features/app/components/layout/sidebar_nav/GuildFolderItem.tsx
@@ -759,14 +759,14 @@ export const GuildFolderItem = observer((props: GuildFolderItemProps) => {
);
});
+const MINI_GUILD_INITIALS_MAX_LENGTH = 2;
+
interface MiniGuildIconProps {
readonly guild: Guild;
}
const MiniGuildIcon = observer(({guild}: MiniGuildIconProps) => {
const iconURL = AvatarSourceUtils.getGuildIconURL(guild, false);
- const initials = getInitialsFromName(guild.name);
- const displayInitials = truncateInitials(initials, 2);
if (iconURL !== '') {
return (
{
className={styles.miniGuildInitials}
data-flx="app.sidebar-nav.guild-folder-item.mini-guild-icon.mini-guild-initials"
>
- {displayInitials}
+ {truncateInitials(getInitialsFromName(guild.name), MINI_GUILD_INITIALS_MAX_LENGTH)}
);
diff --git a/fluxer_app/src/features/app/components/skeleton/GuildRailSkeleton.module.css b/fluxer_app/src/features/app/components/skeleton/GuildRailSkeleton.module.css
index 5219e9e19..92e06702a 100644
--- a/fluxer_app/src/features/app/components/skeleton/GuildRailSkeleton.module.css
+++ b/fluxer_app/src/features/app/components/skeleton/GuildRailSkeleton.module.css
@@ -59,7 +59,6 @@
}
.itemSlot {
- --guild-rail-skeleton-badge-size: var(--mention-badge-small-size);
position: relative;
display: flex;
align-items: center;
@@ -82,18 +81,6 @@
min-height: var(--guild-list-item-box-size);
}
-.mentionBadge {
- position: absolute;
- right: calc(50% - var(--guild-icon-size) / 2 - 0.25rem);
- bottom: calc(50% - var(--guild-icon-size) / 2 - 0.25rem);
- box-shadow: 0 0 0 0.1875rem var(--guild-badge-surface);
-}
-
-.folderSlot .mentionBadge {
- right: -0.25rem;
- bottom: -0.25rem;
-}
-
.itemsTrailingGapCancel {
margin-bottom: calc(-1 * var(--guild-list-item-gap));
}
diff --git a/fluxer_app/src/features/app/components/skeleton/GuildRailSkeleton.tsx b/fluxer_app/src/features/app/components/skeleton/GuildRailSkeleton.tsx
index 83257635b..57109312b 100644
--- a/fluxer_app/src/features/app/components/skeleton/GuildRailSkeleton.tsx
+++ b/fluxer_app/src/features/app/components/skeleton/GuildRailSkeleton.tsx
@@ -34,7 +34,6 @@ import {useState} from 'react';
const GUILD_ICON_SIZE = skeletonSurfaceVar('--guild-icon-size');
const FOLDER_ICON_SIZE = skeletonSurfaceVar('--guild-list-item-box-size');
-const MENTION_BADGE_SIZE = 'var(--guild-rail-skeleton-badge-size)';
const GUILD_PLACEHOLDER_COUNT = 6;
const SHOWS_DOWNLOAD_ACTION = !Platform.isElectron && !Platform.isPWA;
const FALLBACK_ORGANIZED_ITEMS: ReadonlyArray = Object.freeze(
@@ -190,17 +189,6 @@ function showsSkeletonIndicator({indicator, selected}: SkeletonIndicatorVisibili
return indicator !== SkeletonGuildRailItemIndicator.NONE;
}
-const MentionBadgePlaceholder = () => (
-
-);
-
const GuildPlaceholder = ({
indicator,
selected,
@@ -223,9 +211,6 @@ const GuildPlaceholder = ({
size={GUILD_ICON_SIZE}
data-flx="app.skeleton.guild-rail-skeleton.guild-placeholder.skeleton-circle"
/>
- {indicator === SkeletonGuildRailItemIndicator.MENTION && (
-
- )}
);
@@ -326,9 +311,6 @@ const CollapsedFolderPlaceholder = ({
)}
- {indicator === SkeletonGuildRailItemIndicator.MENTION && (
-
- )}
);
@@ -354,9 +336,6 @@ const ExpandedFolderGuildPlaceholder = ({
size={GUILD_ICON_SIZE}
data-flx="app.skeleton.guild-rail-skeleton.expanded-folder-guild-placeholder.skeleton-circle"
/>
- {indicator === SkeletonGuildRailItemIndicator.MENTION && (
-
- )}
);
diff --git a/fluxer_app/src/features/ui/components/MentionBadge.module.css b/fluxer_app/src/features/ui/components/MentionBadge.module.css
index 5390c063b..6ae2f9235 100644
--- a/fluxer_app/src/features/ui/components/MentionBadge.module.css
+++ b/fluxer_app/src/features/ui/components/MentionBadge.module.css
@@ -32,5 +32,17 @@
}
.animatedWrapper {
+ position: relative;
+ display: flex;
pointer-events: none;
+ isolation: isolate;
+}
+
+.animatedWrapper::before {
+ content: '';
+ position: absolute;
+ inset: -0.1875rem;
+ z-index: -1;
+ border-radius: calc(var(--radius-md) + 0.1875rem);
+ background-color: var(--mention-badge-cutout-surface, transparent);
}
diff --git a/fluxer_app/src/features/ui/components/MentionBadge.tsx b/fluxer_app/src/features/ui/components/MentionBadge.tsx
index a3192da22..ef984fd50 100644
--- a/fluxer_app/src/features/ui/components/MentionBadge.tsx
+++ b/fluxer_app/src/features/ui/components/MentionBadge.tsx
@@ -2,12 +2,49 @@
import Accessibility from '@app/features/accessibility/state/Accessibility';
import styles from '@app/features/ui/components/MentionBadge.module.css';
+import {
+ AnimePresence,
+ type AnimeTarget,
+ type AnimeTween,
+ AnimeTweenType,
+ createAnimeFlxElement,
+} from '@app/features/ui/motion/AnimeElement';
import {getCurrentLocale} from '@app/features/user/utils/LocaleUtils';
import {formatCompactNumber, formatNumber} from '@pkgs/number_utils/src/NumberFormatting';
import {clsx} from 'clsx';
-import {AnimatePresence, motion} from 'framer-motion';
import {observer} from 'mobx-react-lite';
+const MentionBadgeSurface = createAnimeFlxElement('flx-ui-mention-badge-surface');
+
+const HIDDEN_MENTION_BADGE: AnimeTarget = Object.freeze({opacity: 0, scale: 0.85});
+const VISIBLE_MENTION_BADGE: AnimeTarget = Object.freeze({opacity: 1, scale: 1});
+
+interface MentionBadgeMotion {
+ readonly from: AnimeTarget;
+ readonly to: AnimeTarget;
+ readonly tween: AnimeTween;
+}
+
+const MENTION_BADGE_LEAVE = false as const;
+
+const STANDARD_MENTION_BADGE_MOTION: MentionBadgeMotion = Object.freeze({
+ from: HIDDEN_MENTION_BADGE,
+ to: VISIBLE_MENTION_BADGE,
+ tween: Object.freeze({type: AnimeTweenType.SPRING, stiffness: 500, damping: 22}),
+});
+const REDUCED_MENTION_BADGE_MOTION: MentionBadgeMotion = Object.freeze({
+ from: VISIBLE_MENTION_BADGE,
+ to: VISIBLE_MENTION_BADGE,
+ tween: Object.freeze({duration: 0}),
+});
+
+function resolveMentionBadgeMotion(useReducedMotion: boolean): MentionBadgeMotion {
+ if (useReducedMotion) {
+ return REDUCED_MENTION_BADGE_MOTION;
+ }
+ return STANDARD_MENTION_BADGE_MOTION;
+}
+
const formatMentionCount = (mentionCount: number) => {
const locale = getCurrentLocale();
if (mentionCount > 99 && mentionCount < 1000) {
@@ -38,34 +75,25 @@ export const MentionBadge = observer(({mentionCount, size = 'medium'}: MentionBa
);
});
export const MentionBadgeAnimated = observer(({mentionCount, size = 'medium'}: MentionBadgeProps) => {
- const shouldAnimate = !Accessibility.useReducedMotion;
- if (!shouldAnimate) {
- return mentionCount > 0 ? (
-
- ) : null;
- }
+ const motion = resolveMentionBadgeMotion(Accessibility.useReducedMotion);
return (
-
+
{mentionCount > 0 && (
-
-
+
)}
-
+
);
});