fix(app): keep sticker sizes fixed while resizing the expression picker

This commit is contained in:
Hampus authored and GitHub committed 2026-08-27 12:59:38 +02:00
1 parent 58732f7770
commit 154e223284
4 files changed
+55 -17

No files matched your search

@@ -14,10 +14,11 @@ import {useVirtualRows} from '@app/features/channel/components/sticker_picker/ho
import {StickerPickerCategoryList} from '@app/features/channel/components/sticker_picker/StickerPickerCategoryList'; import {StickerPickerCategoryList} from '@app/features/channel/components/sticker_picker/StickerPickerCategoryList';
import { import {
buildStickerRowOffsets, buildStickerRowOffsets,
getStickerRowHeight, getFixedStickerRowHeight,
getStickerGridColumns,
getStickerRowWindow, getStickerRowWindow,
STICKER_GRID_TRACK_WIDTH,
STICKER_SECTION_GAP, STICKER_SECTION_GAP,
STICKERS_PER_ROW,
type StickerRowKind, type StickerRowKind,
} from '@app/features/channel/components/sticker_picker/StickerPickerConstants'; } from '@app/features/channel/components/sticker_picker/StickerPickerConstants';
import {StickerPickerInspector} from '@app/features/channel/components/sticker_picker/StickerPickerInspector'; import {StickerPickerInspector} from '@app/features/channel/components/sticker_picker/StickerPickerInspector';
@@ -74,12 +75,12 @@ export const StickersPicker = observer(
const [selectedColumn, setSelectedColumn] = useState(-1); const [selectedColumn, setSelectedColumn] = useState(-1);
const [shouldScrollOnSelection, setShouldScrollOnSelection] = useState(false); const [shouldScrollOnSelection, setShouldScrollOnSelection] = useState(false);
const scrollerRef = useRef<ScrollerHandle>(null); const scrollerRef = useRef<ScrollerHandle>(null);
const {scrollTop, handleScroll, handleResize} = useScrollerViewport(scrollerRef); const {viewportSize, scrollTop, handleScroll, handleResize} = useScrollerViewport(scrollerRef);
const searchInputRef = useRef<HTMLInputElement>(null); const searchInputRef = useRef<HTMLInputElement>(null);
const stickerRefs = useRef<Map<string, HTMLButtonElement>>(new Map()); const stickerRefs = useRef<Map<string, HTMLButtonElement>>(new Map());
const channel = channelId ? (Channels.getChannel(channelId) ?? null) : null; const channel = channelId ? (Channels.getChannel(channelId) ?? null) : null;
const rowListRef = useRef<HTMLDivElement>(null); const rowListRef = useRef<HTMLDivElement>(null);
const [listMetrics, setListMetrics] = useState({origin: 0, viewportHeight: 0, gridWidth: 0}); const [listMetrics, setListMetrics] = useState({origin: 0, viewportHeight: 0});
const [stickerDataVersion, setStickerDataVersion] = useState(0); const [stickerDataVersion, setStickerDataVersion] = useState(0);
const permissionVersion = useSyncExternalStore(Permission.subscribe.bind(Permission), () => Permission.version); const permissionVersion = useSyncExternalStore(Permission.subscribe.bind(Permission), () => Permission.version);
const {shouldAnimate: shouldAnimateStickerPreview} = useStickerAnimation(); const {shouldAnimate: shouldAnimateStickerPreview} = useStickerAnimation();
@@ -138,13 +139,26 @@ export const StickersPicker = observer(
allUpsell.accessibleItems, allUpsell.accessibleItems,
renderedStickers, renderedStickers,
); );
const zoomLevel = Accessibility.zoomLevel;
const gridColumns = useMemo(() => getStickerGridColumns(viewportSize.width), [viewportSize.width, zoomLevel]);
const previousGridColumnsRef = useRef(gridColumns);
useLayoutEffect(() => {
if (previousGridColumnsRef.current === gridColumns) {
return;
}
previousGridColumnsRef.current = gridColumns;
setHoveredSticker(null);
setSelectedRow(-1);
setSelectedColumn(-1);
setShouldScrollOnSelection(false);
}, [gridColumns]);
const pickerRows = useVirtualRows( const pickerRows = useVirtualRows(
searchTerm, searchTerm,
renderedStickers, renderedStickers,
favoriteStickers, favoriteStickers,
frequentlyUsedStickers, frequentlyUsedStickers,
stickersByGuildId, stickersByGuildId,
STICKERS_PER_ROW, gridColumns,
); );
const hasNoStickersAtAll = allItems.length === 0; const hasNoStickersAtAll = allItems.length === 0;
const isSearching = searchTerm.trim().length > 0; const isSearching = searchTerm.trim().length > 0;
@@ -198,12 +212,8 @@ export const StickersPicker = observer(
} }
return {stickerRowIndexes: indexes, stickerRowStarts: starts, categoryRowIndexes: categories}; return {stickerRowIndexes: indexes, stickerRowStarts: starts, categoryRowIndexes: categories};
}, [pickerRows]); }, [pickerRows]);
const zoomLevel = Accessibility.zoomLevel;
const remScale = getAppRemScale(); const remScale = getAppRemScale();
const stickerRowHeight = useMemo( const stickerRowHeight = useMemo(() => getFixedStickerRowHeight(remScale), [remScale, zoomLevel]);
() => getStickerRowHeight(listMetrics.gridWidth, STICKERS_PER_ROW, remScale),
[listMetrics.gridWidth, remScale, zoomLevel],
);
const rowOffsets = useMemo(() => { const rowOffsets = useMemo(() => {
const rowKinds = pickerRows.map((row): StickerRowKind => row.type); const rowKinds = pickerRows.map((row): StickerRowKind => row.type);
return buildStickerRowOffsets(rowKinds, {remScale, stickerRowHeight, sectionGap: STICKER_SECTION_GAP}); return buildStickerRowOffsets(rowKinds, {remScale, stickerRowHeight, sectionGap: STICKER_SECTION_GAP});
@@ -219,11 +229,8 @@ export const StickersPicker = observer(
rowListNode.getBoundingClientRect().top - scrollerNode.getBoundingClientRect().top + scrollerNode.scrollTop, rowListNode.getBoundingClientRect().top - scrollerNode.getBoundingClientRect().top + scrollerNode.scrollTop,
); );
const viewportHeight = scrollerNode.clientHeight; const viewportHeight = scrollerNode.clientHeight;
const gridWidth = rowListNode.clientWidth;
setListMetrics((current) => setListMetrics((current) =>
current.origin === origin && current.viewportHeight === viewportHeight && current.gridWidth === gridWidth current.origin === origin && current.viewportHeight === viewportHeight ? current : {origin, viewportHeight},
? current
: {origin, viewportHeight, gridWidth},
); );
}); });
const rowWindow = useMemo( const rowWindow = useMemo(
@@ -394,7 +401,8 @@ export const StickersPicker = observer(
row={row} row={row}
handleHover={handleHover} handleHover={handleHover}
handleSelect={handleStickerSelect} handleSelect={handleStickerSelect}
gridColumns={STICKERS_PER_ROW} gridColumns={gridColumns}
cellTrackWidth={STICKER_GRID_TRACK_WIDTH}
selectedRow={selectedRow} selectedRow={selectedRow}
selectedColumn={selectedColumn} selectedColumn={selectedColumn}
stickerRowIndex={stickerRowIndexes[rowIndex]!} stickerRowIndex={stickerRowIndexes[rowIndex]!}
@@ -1,9 +1,12 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
import {getRemScaleForDocument} from '@app/features/theme/layout/RemFromPx';
export const STICKERS_PER_ROW = 4; export const STICKERS_PER_ROW = 4;
export const STICKERS_PER_ROW_MOBILE = 5; export const STICKERS_PER_ROW_MOBILE = 5;
export const STICKER_CATEGORY_HEADER_HEIGHT = 32; export const STICKER_CATEGORY_HEADER_HEIGHT = 32;
export const STICKER_SECTION_GAP = 12; export const STICKER_SECTION_GAP = 12;
export const STICKER_GRID_TRACK_WIDTH = 120;
const STICKER_CATEGORY_HEADER_GAP = 8; const STICKER_CATEGORY_HEADER_GAP = 8;
const STICKER_GRID_GAP = 8; const STICKER_GRID_GAP = 8;
@@ -23,6 +26,20 @@ export interface StickerRowWindow {
lastRow: number; lastRow: number;
} }
export function getStickerGridColumns(containerWidth: number): number {
if (containerWidth <= 0) {
return STICKERS_PER_ROW;
}
const remScale = getRemScaleForDocument(typeof document === 'undefined' ? null : document);
const trackWidth = STICKER_GRID_TRACK_WIDTH * remScale;
const gap = STICKER_GRID_GAP * remScale;
return Math.max(1, Math.floor((containerWidth + gap) / (trackWidth + gap)));
}
export function getFixedStickerRowHeight(remScale: number): number {
return (STICKER_GRID_TRACK_WIDTH + STICKER_GRID_BLOCK_END_PADDING) * remScale;
}
export function getStickerRowHeight(gridWidth: number, gridColumns: number, remScale: number): number { export function getStickerRowHeight(gridWidth: number, gridColumns: number, remScale: number): number {
if (!(gridWidth > 0) || gridColumns <= 0) { if (!(gridWidth > 0) || gridColumns <= 0) {
return 0; return 0;
@@ -20,6 +20,11 @@
padding: 0 0 0.5rem; padding: 0 0 0.5rem;
} }
.stickerGridFixedTrack {
grid-auto-flow: column;
justify-content: start;
}
.stickerButton { .stickerButton {
position: relative; position: relative;
display: flex; display: flex;
@@ -27,6 +27,7 @@ interface VirtualRowRendererProps {
handleHover: (sticker: GuildSticker | null, row?: number, column?: number) => void; handleHover: (sticker: GuildSticker | null, row?: number, column?: number) => void;
handleSelect: (sticker: GuildSticker, shiftKey?: boolean) => void; handleSelect: (sticker: GuildSticker, shiftKey?: boolean) => void;
gridColumns?: number; gridColumns?: number;
cellTrackWidth?: number;
selectedRow: number; selectedRow: number;
selectedColumn: number; selectedColumn: number;
stickerRowIndex: number; stickerRowIndex: number;
@@ -126,6 +127,7 @@ const VirtualRowRendererBase: React.FC<VirtualRowRendererProps> = React.memo(
handleHover, handleHover,
handleSelect, handleSelect,
gridColumns = 4, gridColumns = 4,
cellTrackWidth,
selectedRow, selectedRow,
selectedColumn, selectedColumn,
stickerRowIndex, stickerRowIndex,
@@ -220,11 +222,14 @@ const VirtualRowRendererBase: React.FC<VirtualRowRendererProps> = React.memo(
); );
} }
if (row.type === 'sticker-row') { if (row.type === 'sticker-row') {
const hasFixedTrack = cellTrackWidth != null && cellTrackWidth > 0;
return ( return (
<div <div
className={styles.stickerGrid} className={hasFixedTrack ? `${styles.stickerGrid} ${styles.stickerGridFixedTrack}` : styles.stickerGrid}
style={{ style={{
gridTemplateColumns: `repeat(${gridColumns}, minmax(0, 1fr))`, gridTemplateColumns: hasFixedTrack
? `repeat(auto-fill, ${remFromPx(cellTrackWidth)})`
: `repeat(${gridColumns}, minmax(0, 1fr))`,
}} }}
data-flx="channel.sticker-picker.virtual-row.virtual-row-renderer-base.sticker-grid" data-flx="channel.sticker-picker.virtual-row.virtual-row-renderer-base.sticker-grid"
> >
@@ -286,6 +291,7 @@ interface VirtualRowWrapperProps {
handleHover: (sticker: GuildSticker | null, row?: number, column?: number) => void; handleHover: (sticker: GuildSticker | null, row?: number, column?: number) => void;
handleSelect: (sticker: GuildSticker, shiftKey?: boolean) => void; handleSelect: (sticker: GuildSticker, shiftKey?: boolean) => void;
gridColumns?: number; gridColumns?: number;
cellTrackWidth?: number;
selectedRow: number; selectedRow: number;
selectedColumn: number; selectedColumn: number;
stickerRowIndex: number; stickerRowIndex: number;
@@ -300,6 +306,7 @@ export const VirtualRowWrapper: React.FC<VirtualRowWrapperProps> = observer(
handleHover, handleHover,
handleSelect, handleSelect,
gridColumns, gridColumns,
cellTrackWidth,
selectedRow, selectedRow,
selectedColumn, selectedColumn,
stickerRowIndex, stickerRowIndex,
@@ -313,6 +320,7 @@ export const VirtualRowWrapper: React.FC<VirtualRowWrapperProps> = observer(
handleHover={handleHover} handleHover={handleHover}
handleSelect={handleSelect} handleSelect={handleSelect}
gridColumns={gridColumns} gridColumns={gridColumns}
cellTrackWidth={cellTrackWidth}
selectedRow={selectedRow} selectedRow={selectedRow}
selectedColumn={selectedColumn} selectedColumn={selectedColumn}
stickerRowIndex={stickerRowIndex} stickerRowIndex={stickerRowIndex}