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 && ( - - + )} - + ); });