diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 181f5be819..48848597ed 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -3698,6 +3698,14 @@ "messageformat": "No stickers installed", "description": "Shown in the sticker pack manager when you don't have any installed sticker packs." }, + "icu:stickers--StickerManager--MyStickers--None": { + "messageformat": "No sticker packs, add stickers to send to your friends.", + "description": "Text shown in the sticker pack manager on the installed stickers tab when you don't have any installed sticker packs." + }, + "icu:stickers--StickerManager--AddStickers": { + "messageformat": "Add stickers", + "description": "Button in the sticker pack manager on the installed stickers tab when you don't have any installed sticker packs. The button takes you to the All Stickers tab where you can install stickers." + }, "icu:stickers--StickerManager--BlessedPacks": { "messageformat": "Signal Artist Series", "description": "Shown in the sticker pack manager above the default sticker packs." @@ -3710,6 +3718,14 @@ "messageformat": "Stickers You Received", "description": "Shown in the sticker pack manager above sticker packs which you have received in messages." }, + "icu:stickers--StickerManager--ReceivedPacks2": { + "messageformat": "Shared With You", + "description": "Shown in the sticker pack manager above sticker packs which you have received in messages." + }, + "icu:stickers--StickerManager--ReceivedPacksDescription": { + "messageformat": "When you receive a sticker from someone, the sticker pack will appear here.", + "description": "Section description shown in the sticker pack manager above sticker packs which you have received in messages." + }, "icu:stickers--StickerManager--ReceivedPacks--Empty": { "messageformat": "Stickers from incoming messages will appear here", "description": "Shown in the sticker pack manager when you have not received any sticker packs in messages." @@ -3718,6 +3734,10 @@ "messageformat": "Install", "description": "Shown in the sticker pack manager next to sticker packs which can be installed." }, + "icu:stickers--StickerManager--Installed": { + "messageformat": "Installed", + "description": "Accessibility label in the sticker pack manager next to sticker packs which are already installed." + }, "icu:stickers--StickerManager--Uninstall": { "messageformat": "Uninstall", "description": "Shown in the sticker pack manager next to sticker packs which are already installed." @@ -3726,6 +3746,22 @@ "messageformat": "You may not be able to re-install this sticker pack if you no longer have the source message.", "description": "Shown in the sticker pack manager next to sticker packs which are already installed." }, + "icu:stickers--StickerManagerHeader--All": { + "messageformat": "All", + "description": "Shown in the sticker pack manager in the navigation tabs. This tab shows all sticker packs available to install." + }, + "icu:stickers--StickerManagerHeader--MyStickers": { + "messageformat": "My Stickers", + "description": "Shown in the sticker pack manager in the navigation tabs. This tab shows all sticker packs you have currently installed." + }, + "icu:stickers--StickerManagerPackContextMenu--Add": { + "messageformat": "Add", + "description": "Context menu item to add a sticker pack. Shown in the sticker pack manager when right clicking a sticker pack." + }, + "icu:stickers--StickerManagerPackContextMenu--Remove": { + "messageformat": "Remove", + "description": "Context menu item to remove a sticker pack. Shown in the sticker pack manager when right clicking a sticker pack." + }, "icu:stickers--StickerPreview--Title": { "messageformat": "Sticker Pack", "description": "The title that appears in the sticker pack preview modal." diff --git a/stylesheets/components/StickerManager.scss b/stylesheets/components/StickerManager.scss index 42d65aaa77..774466ddf7 100644 --- a/stylesheets/components/StickerManager.scss +++ b/stylesheets/components/StickerManager.scss @@ -10,95 +10,13 @@ outline: none; } -.module-sticker-manager__text { - height: 18px; - - letter-spacing: 0px; - line-height: 18px; - padding-inline-start: 8px; - - @include mixins.light-theme() { - color: variables.$color-gray-60; - } - - @include mixins.dark-theme() { - color: variables.$color-gray-25; - } - - &--heading { - @include mixins.font-body-1-bold; - - @include mixins.light-theme() { - color: variables.$color-gray-90; - } - - @include mixins.dark-theme() { - color: variables.$color-gray-05; - } - } -} - -.module-sticker-manager__empty { - display: flex; - justify-content: center; - align-items: center; - height: 64px; - border-radius: 8px; - - @include mixins.light-theme { - background: variables.$color-gray-02; - color: variables.$color-gray-60; - } - - @include mixins.dark-theme { - background: variables.$color-gray-90; - color: variables.$color-gray-25; - } -} - -%blessed-sticker-pack-icon { - height: 14px; - width: 14px; - border-radius: 8px; - background-color: variables.$color-white; - display: inline-block; - vertical-align: middle; - margin-inline-start: 5px; - margin-bottom: 2px; - position: relative; - - &::before { - content: ''; - display: block; - width: 16px; - height: 16px; - position: absolute; - top: -1px; - inset-inline-start: -1px; - - @include mixins.light-theme { - @include mixins.color-svg( - '../images/icons/v3/check/check-circle-fill.svg', - variables.$color-accent-blue - ); - } - - @include mixins.dark-theme { - @include mixins.color-svg( - '../images/icons/v3/check/check-circle-fill.svg', - variables.$color-accent-blue - ); - } - } -} - .module-sticker-manager__pack-row { @include mixins.button-reset; & { display: flex; flex-direction: row; - padding: 16px; + padding: 10px; padding-inline-start: 8px; } @@ -109,13 +27,13 @@ } &__cover { - width: 48px; - height: 48px; + width: 36px; + height: 36px; object-fit: contain; } &__cover-placeholder { - width: 48px; - height: 48px; + width: 36px; + height: 36px; background: variables.$color-gray-05; } @@ -128,26 +46,6 @@ padding-block: 0; padding-inline: 12px; } - - &__title { - flex: 1; - } - - &__author { - flex: 1; - - @include mixins.light-theme() { - color: variables.$color-gray-45; - } - - @include mixins.dark-theme() { - color: variables.$color-gray-25; - } - } - - &__blessed-icon { - @extend %blessed-sticker-pack-icon; - } } &__controls { @@ -182,40 +80,3 @@ } } } - -.module-sticker-manager__install-button { - background: none; - border: 0; - color: variables.$color-gray-90; - - @include mixins.font-body-1-bold; - - height: 24px; - background: variables.$color-gray-05; - border-radius: 12px; - display: flex; - justify-content: center; - align-items: center; - padding-block: 0; - padding-inline: 12px; - - @include mixins.dark-theme { - color: variables.$color-gray-05; - background: variables.$color-gray-75; - } - - @include mixins.mouse-mode { - outline: none; - } - - &--blue { - @include mixins.light-theme { - background: variables.$color-ultramarine; - color: variables.$color-white; - } - @include mixins.dark-theme { - background: variables.$color-ultramarine-light; - color: variables.$color-white; - } - } -} diff --git a/ts/components/stickers/StickerManager.dom.stories.tsx b/ts/components/stickers/StickerManager.dom.stories.tsx index 94767df8af..427da40561 100644 --- a/ts/components/stickers/StickerManager.dom.stories.tsx +++ b/ts/components/stickers/StickerManager.dom.stories.tsx @@ -12,11 +12,18 @@ import { sticker1, sticker2, } from '../../test-helpers/stickersMocks.std.ts'; +import type { StickerPackType } from '../../state/ducks/stickers.preload.ts'; const { i18n } = window.SignalContext; export default { title: 'Components/Stickers/StickerManager', + argTypes: { + tab: { + options: ['all', 'my-stickers'], + control: { type: 'select' }, + }, + }, } satisfies Meta; const receivedPacks = [ @@ -31,11 +38,21 @@ const installedPacks = [ const blessedPacks = [ createPack( - { id: 'blessed-pack-1', status: 'downloaded', isBlessed: true }, + { + id: 'blessed-pack-1', + status: 'downloaded', + isBlessed: true, + author: 'Ann Chovy', + }, sticker1 ), createPack( - { id: 'blessed-pack-2', status: 'downloaded', isBlessed: true }, + { + id: 'blessed-pack-2', + status: 'downloaded', + isBlessed: true, + author: 'Tom Ato', + }, sticker2 ), ]; @@ -54,36 +71,51 @@ const createProps = (overrideProps: Partial = {}): Props => ({ installedPacks: overrideProps.installedPacks || [], knownPacks: overrideProps.knownPacks || [], receivedPacks: overrideProps.receivedPacks || [], + tab: overrideProps.tab || 'all', + setTab: action('setTab'), showToast: action('showToast'), uninstallStickerPack: action('uninstallStickerPack'), }); -export function Full(): JSX.Element { - const props = createProps({ installedPacks, receivedPacks, blessedPacks }); +export function Full(args: Props): JSX.Element { + const props = createProps({ blessedPacks, installedPacks, receivedPacks }); - return ; + return ; } -export function InstalledPacks(): JSX.Element { - const props = createProps({ installedPacks }); +export function ReceivedPacks(args: Props): JSX.Element { + const props = createProps({ blessedPacks, receivedPacks }); - return ; + return ; } -export function ReceivedPacks(): JSX.Element { - const props = createProps({ receivedPacks }); +export function InstalledPacks(args: Props): JSX.Element { + const blessedPacksWithInstalled = [ + { ...blessedPacks[0], status: 'installed' }, + blessedPacks[1], + ] as Array; + const props = createProps({ + blessedPacks: blessedPacksWithInstalled, + installedPacks, + receivedPacks: installedPacks, + }); - return ; + return ; } -export function InstalledAndKnownPacks(): JSX.Element { - const props = createProps({ installedPacks, knownPacks }); +export function InstalledAndKnownPacks(args: Props): JSX.Element { + const props = createProps({ + blessedPacks, + knownPacks, + installedPacks, + receivedPacks: installedPacks, + }); - return ; + return ; } -export function Empty(): JSX.Element { +export function Empty(args: Props): JSX.Element { const props = createProps(); - return ; + return ; } diff --git a/ts/components/stickers/StickerManager.dom.tsx b/ts/components/stickers/StickerManager.dom.tsx index 9dc632e9c4..5155a01198 100644 --- a/ts/components/stickers/StickerManager.dom.tsx +++ b/ts/components/stickers/StickerManager.dom.tsx @@ -8,13 +8,16 @@ import { useEffect, useCallback, useMemo, + type JSX, } from 'react'; import { StickerManagerPackRow } from './StickerManagerPackRow.dom.tsx'; import { StickerPreviewModal } from './StickerPreviewModal.dom.tsx'; import type { LocalizerType } from '../../types/Util.std.ts'; import type { StickerPackType } from '../../state/ducks/stickers.preload.ts'; import type { ShowToastAction } from '../../state/ducks/toast.preload.ts'; -import { Tabs } from '../Tabs.dom.tsx'; +import type { StickerManagerTabType } from '../../types/Stickers.preload.ts'; +import { tw } from '../../axo/tw.dom.tsx'; +import { AxoButton } from '../../axo/AxoButton.dom.tsx'; export type OwnProps = { readonly blessedPacks: ReadonlyArray; @@ -33,6 +36,8 @@ export type OwnProps = { readonly installedPacks: ReadonlyArray; readonly knownPacks?: ReadonlyArray; readonly receivedPacks: ReadonlyArray; + readonly tab: StickerManagerTabType; + readonly setTab: (value: StickerManagerTabType) => void; readonly showToast: ShowToastAction; readonly uninstallStickerPack: ( packId: string, @@ -43,11 +48,6 @@ export type OwnProps = { export type Props = OwnProps; -enum TabViews { - Available = 'Available', - Installed = 'Installed', -} - export const StickerManager = memo(function StickerManagerInner({ blessedPacks, closeStickerPackPreview, @@ -57,6 +57,8 @@ export const StickerManager = memo(function StickerManagerInner({ installedPacks, knownPacks, receivedPacks, + tab, + setTab, showToast, uninstallStickerPack, }: Props) { @@ -90,6 +92,22 @@ export const StickerManager = memo(function StickerManagerInner({ setPackIdToPreview(packId); }, []); + const renderStickerPackRow = useCallback( + (pack: StickerPackType): JSX.Element => ( + + ), + [i18n, installStickerPack, previewPack, uninstallStickerPack] + ); + + const setTabAll = useCallback(() => setTab('all'), [setTab]); + const allPacks = useMemo(() => { const packsMap = new Map(); const packsList = [ @@ -99,6 +117,10 @@ export const StickerManager = memo(function StickerManagerInner({ ...receivedPacks, ]; packsList.forEach(pack => { + if (packsMap.get(pack.id)) { + return; + } + packsMap.set(pack.id, pack); }); return packsMap; @@ -123,95 +145,83 @@ export const StickerManager = memo(function StickerManagerInner({ /> ) : null}
- - {({ selectedTab }) => ( - <> - {selectedTab === TabViews.Available && ( - <> -

- {i18n('icu:stickers--StickerManager--BlessedPacks')} -

- {blessedPacks.length > 0 ? ( - blessedPacks.map(pack => ( - - )) - ) : ( -
- {i18n( - 'icu:stickers--StickerManager--BlessedPacks--Empty' - )} -
- )} - -

- {i18n('icu:stickers--StickerManager--ReceivedPacks')} -

- {receivedPacks.length > 0 ? ( - receivedPacks.map(pack => ( - - )) - ) : ( -
- {i18n( - 'icu:stickers--StickerManager--ReceivedPacks--Empty' - )} -
- )} - + {tab === 'all' && ( + <> +

0 ? ( - installedPacks.map(pack => ( - - )) - ) : ( -
- {i18n( - 'icu:stickers--StickerManager--InstalledPacks--Empty' - )} -
- ))} - - )} - + > + {i18n('icu:stickers--StickerManager--BlessedPacks')} +

+ {blessedPacks.length > 0 ? ( + blessedPacks.map(pack => renderStickerPackRow(pack)) + ) : ( +

+ {i18n('icu:stickers--StickerManager--BlessedPacks--Empty')} +

+ )} +
+ + {receivedPacks.length > 0 && ( + <> +

+ {i18n('icu:stickers--StickerManager--ReceivedPacks2')} +

+

+ {i18n( + 'icu:stickers--StickerManager--ReceivedPacksDescription' + )} +

+ {receivedPacks.map(pack => renderStickerPackRow(pack))} + + )} + + )} + {tab === 'my-stickers' && + (installedPacks.length > 0 ? ( + installedPacks.map(pack => renderStickerPackRow(pack)) + ) : ( +
+
+
+ {i18n('icu:stickers--StickerManager--MyStickers--None')} +
+
+ + {i18n('icu:stickers--StickerManager--AddStickers')} + +
+
+
+ ))}
); diff --git a/ts/components/stickers/StickerManagerHeader.dom.tsx b/ts/components/stickers/StickerManagerHeader.dom.tsx new file mode 100644 index 0000000000..4875b5dd33 --- /dev/null +++ b/ts/components/stickers/StickerManagerHeader.dom.tsx @@ -0,0 +1,100 @@ +// Copyright 2026 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { useCallback, type JSX } from 'react'; + +import { tw } from '../../axo/tw.dom.tsx'; +import { ExperimentalAxoSegmentedControl } from '../../axo/AxoSegmentedControl.dom.tsx'; +import { AxoSelect } from '../../axo/AxoSelect.dom.tsx'; +import type { LocalizerType } from '../../types/Util.std.ts'; +import type { StickerManagerTabType } from '../../types/Stickers.preload.ts'; + +// Provided by smart layer +export type Props = Readonly<{ + i18n: LocalizerType; + tab: StickerManagerTabType; + setTab: (newTab: StickerManagerTabType) => void; +}>; + +const TabValue = { + All: 'all', + MyStickers: 'my-stickers', +} as const satisfies Record; + +export function StickerManagerHeader({ + i18n, + tab, + setTab, +}: Props): JSX.Element { + const setSelectedTabWithDefault = useCallback( + (value: string | null) => { + switch (value) { + case 'my-stickers': + setTab(value); + break; + case null: + break; + default: + setTab('all'); + break; + } + }, + [setTab] + ); + + return ( +
+
+ +
+ + + + {i18n('icu:stickers--StickerManagerHeader--All')} + + + + + {i18n('icu:stickers--StickerManagerHeader--MyStickers')} + + + +
+ +
+ + + + + + {i18n('icu:stickers--StickerManagerHeader--All')} + + + + + {i18n('icu:stickers--StickerManagerHeader--MyStickers')} + + + + +
+ +
+ +
+
+ ); +} diff --git a/ts/components/stickers/StickerManagerPackRow.dom.tsx b/ts/components/stickers/StickerManagerPackRow.dom.tsx index 0b1c828d44..ce3f0a8a85 100644 --- a/ts/components/stickers/StickerManagerPackRow.dom.tsx +++ b/ts/components/stickers/StickerManagerPackRow.dom.tsx @@ -7,12 +7,16 @@ import { useCallback, type MouseEvent, type KeyboardEvent, + type ReactNode, } from 'react'; import type { LocalizerType } from '../../types/Util.std.ts'; import type { StickerPackType } from '../../state/ducks/stickers.preload.ts'; -import { Button, ButtonVariant } from '../Button.dom.tsx'; import { UserText } from '../UserText.dom.tsx'; import { AxoConfirmDialog } from '../../axo/AxoConfirmDialog.dom.tsx'; +import { AxoIconButton } from '../../axo/AxoIconButton.dom.tsx'; +import { tw } from '../../axo/tw.dom.tsx'; +import { OfficialChatInlineBadge } from '../conversation/OfficialChatInlineBadge.dom.tsx'; +import { AxoContextMenu } from '../../axo/AxoContextMenu.dom.tsx'; export type OwnProps = { readonly i18n: LocalizerType; @@ -47,7 +51,7 @@ export const StickerManagerPackRow = memo(function StickerManagerPackRowInner({ }, [setUninstalling]); const handleInstall = useCallback( - (e: MouseEvent) => { + (e: Event | MouseEvent) => { e.stopPropagation(); if (installStickerPack) { installStickerPack(id, key, { actionSource: 'ui' }); @@ -57,7 +61,7 @@ export const StickerManagerPackRow = memo(function StickerManagerPackRowInner({ ); const handleUninstall = useCallback( - (e: MouseEvent) => { + (e: Event) => { e.stopPropagation(); if (isBlessed && uninstallStickerPack) { uninstallStickerPack(id, key, { actionSource: 'ui' }); @@ -116,55 +120,98 @@ export const StickerManagerPackRow = memo(function StickerManagerPackRowInner({ {i18n('icu:stickers--StickerManager--Uninstall')} -
- {pack.cover ? ( - {pack.title} - ) : ( -
- )} -
-
- - {pack.isBlessed ? ( - - ) : null} -
-
- {pack.author} -
-
-
- {pack.status === 'installed' ? ( - +
+ {pack.cover ? ( + {pack.title} ) : ( - +
)} +
+
+ + {pack.isBlessed ? ( + + + + ) : null} +
+
+ {pack.author} +
+
+
+ {pack.status === 'installed' ? ( + + ) : ( + + )} +
-
+ ); }); + +function PackContextMenu(props: { + i18n: LocalizerType; + isInstalled: boolean; + handleInstall: (e: Event) => void; + handleUninstall: (e: Event) => void; + children: ReactNode; +}) { + const { i18n, isInstalled, handleInstall, handleUninstall } = props; + + return ( + + {props.children} + + {isInstalled ? ( + + {i18n('icu:stickers--StickerManagerPackContextMenu--Remove')} + + ) : ( + + {i18n('icu:stickers--StickerManagerPackContextMenu--Add')} + + )} + + + ); +} diff --git a/ts/state/ducks/stickers.preload.ts b/ts/state/ducks/stickers.preload.ts index 2d686ecea1..7aca93afb3 100644 --- a/ts/state/ducks/stickers.preload.ts +++ b/ts/state/ducks/stickers.preload.ts @@ -12,6 +12,7 @@ import { DataReader, DataWriter } from '../../sql/Client.preload.ts'; import type { ActionSourceType, RecentStickerType, + StickerManagerTabType, } from '../../types/Stickers.preload.ts'; import { downloadStickerPack as externalDownloadStickerPack, @@ -42,6 +43,7 @@ export type StickersStateType = ReadonlyDeep<{ packs: Dictionary; recentStickers: Array; blessedPacks: Dictionary; + stickerManagerTab: StickerManagerTabType; }>; // These are for the React components @@ -138,10 +140,16 @@ type UseStickerFulfilledAction = ReadonlyDeep<{ payload: UseStickerPayloadType; }>; +type SetStickerManagerTabAction = ReadonlyDeep<{ + type: 'stickers/SET_STICKER_MANAGER_TAB'; + payload: StickerManagerTabType; +}>; + export type StickersActionType = ReadonlyDeep< | ClearInstalledStickerPackAction | InstallStickerPackFulfilledAction | NoopActionType + | SetStickerManagerTabAction | StickerAddedAction | StickerPackAddedAction | StickerPackRemovedAction @@ -157,6 +165,7 @@ export const actions = { downloadStickerPack, installStickerPack, removeStickerPack, + setStickerManagerTab, stickerAdded, stickerPackAdded, stickerPackUpdated, @@ -381,6 +390,15 @@ async function doUseSticker( }; } +function setStickerManagerTab( + tab: StickerManagerTabType +): SetStickerManagerTabAction { + return { + type: 'stickers/SET_STICKER_MANAGER_TAB', + payload: tab, + }; +} + // Reducer export function getEmptyState(): StickersStateType { @@ -389,6 +407,7 @@ export function getEmptyState(): StickersStateType { packs: {}, recentStickers: [], blessedPacks: {}, + stickerManagerTab: 'all', }; } @@ -557,6 +576,15 @@ export function reducer( }; } + if (action.type === 'stickers/SET_STICKER_MANAGER_TAB') { + const { payload: stickerManagerTab } = action; + + return { + ...state, + stickerManagerTab, + }; + } + if (action.type === ERASE_STORAGE_SERVICE) { const { packs } = state; diff --git a/ts/state/selectors/stickers.std.ts b/ts/state/selectors/stickers.std.ts index aaa89cdcc9..edc89701b8 100644 --- a/ts/state/selectors/stickers.std.ts +++ b/ts/state/selectors/stickers.std.ts @@ -4,7 +4,10 @@ import lodash, { type Dictionary } from 'lodash'; import { createSelector } from 'reselect'; -import type { RecentStickerType } from '../../types/Stickers.preload.ts'; +import type { + RecentStickerType, + StickerManagerTabType, +} from '../../types/Stickers.preload.ts'; import { getLocalAttachmentUrl, AttachmentDisposition, @@ -156,7 +159,9 @@ export const getReceivedStickerPacks = createSelector( return filterAndTransformPacks( packs, pack => - (pack.status === 'downloaded' || pack.status === 'pending') && + (pack.status === 'downloaded' || + pack.status === 'pending' || + pack.status === 'installed') && !blessedPacks[pack.id], pack => pack.createdAt, blessedPacks @@ -173,7 +178,7 @@ export const getBlessedStickerPacks = createSelector( ): Array => { return filterAndTransformPacks( packs, - pack => blessedPacks[pack.id] != null && pack.status !== 'installed', + pack => blessedPacks[pack.id] != null, pack => pack.createdAt, blessedPacks ); @@ -195,3 +200,9 @@ export const getKnownStickerPacks = createSelector( ); } ); + +export const getStickerManagerTab = createSelector( + getStickers, + (stickers: StickersStateType): StickerManagerTabType => + stickers.stickerManagerTab +); diff --git a/ts/state/smart/ConversationPanel.preload.tsx b/ts/state/smart/ConversationPanel.preload.tsx index 3875c74c3a..9472960f54 100644 --- a/ts/state/smart/ConversationPanel.preload.tsx +++ b/ts/state/smart/ConversationPanel.preload.tsx @@ -43,6 +43,7 @@ import { SmartMiniPlayer } from './MiniPlayer.preload.tsx'; import { SmartGroupMemberLabelEditor } from './GroupMemberLabelEditor.preload.tsx'; import { useNavActions } from '../ducks/nav.std.ts'; import { ErrorBoundary } from '../../components/ErrorBoundary.dom.tsx'; +import { SmartStickerManagerHeader } from './StickerManagerHeader.preload.tsx'; const log = createLogger('ConversationPanel'); @@ -305,6 +306,8 @@ const PanelContainer = forwardRef( let info: JSX.Element | undefined; if (panel.type === PanelType.AllMedia) { info = ; + } else if (panel.type === PanelType.StickerManager) { + info = ; } else if (conversationTitle != null) { info = (
diff --git a/ts/state/smart/StickerManager.preload.tsx b/ts/state/smart/StickerManager.preload.tsx index ff7dd0d68c..3bb7728b31 100644 --- a/ts/state/smart/StickerManager.preload.tsx +++ b/ts/state/smart/StickerManager.preload.tsx @@ -10,6 +10,7 @@ import { getInstalledStickerPacks, getKnownStickerPacks, getReceivedStickerPacks, + getStickerManagerTab, } from '../selectors/stickers.std.ts'; import { useStickersActions } from '../ducks/stickers.preload.ts'; import { useGlobalModalActions } from '../ducks/globalModals.preload.ts'; @@ -21,11 +22,13 @@ export const SmartStickerManager = memo(function SmartStickerManager() { const receivedPacks = useSelector(getReceivedStickerPacks); const installedPacks = useSelector(getInstalledStickerPacks); const knownPacks = useSelector(getKnownStickerPacks); + const tab = useSelector(getStickerManagerTab); const { downloadStickerPack, installStickerPack, uninstallStickerPack } = useStickersActions(); const { closeStickerPackPreview } = useGlobalModalActions(); const { showToast } = useToastActions(); + const { setStickerManagerTab } = useStickersActions(); return ( ); }); diff --git a/ts/state/smart/StickerManagerHeader.preload.tsx b/ts/state/smart/StickerManagerHeader.preload.tsx new file mode 100644 index 0000000000..03207a4d54 --- /dev/null +++ b/ts/state/smart/StickerManagerHeader.preload.tsx @@ -0,0 +1,24 @@ +// Copyright 2026 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only +import { memo } from 'react'; +import { useSelector } from 'react-redux'; +import { StickerManagerHeader } from '../../components/stickers/StickerManagerHeader.dom.tsx'; +import { getIntl } from '../selectors/user.std.ts'; +import { getStickerManagerTab } from '../selectors/stickers.std.ts'; +import { useStickersActions } from '../ducks/stickers.preload.ts'; + +export const SmartStickerManagerHeader = memo( + function SmartStickerManagerHeader() { + const i18n = useSelector(getIntl); + const tab = useSelector(getStickerManagerTab); + const { setStickerManagerTab } = useStickersActions(); + + return ( + + ); + } +); diff --git a/ts/test-mock/storage/sticker_test.node.ts b/ts/test-mock/storage/sticker_test.node.ts index f16c977275..93fbc0233b 100644 --- a/ts/test-mock/storage/sticker_test.node.ts +++ b/ts/test-mock/storage/sticker_test.node.ts @@ -190,8 +190,8 @@ describe('stickers', function (this: Mocha.Suite) { '[data-testid=StickerManager]' ); - debug('switching to Installed tab'); - await stickerManager.locator('.Tabs__tab >> "Installed"').click(); + debug('switching to My Stickers tab'); + await window.getByText('My Stickers').click(); { debug('installing first sticker pack via storage service'); diff --git a/ts/types/Stickers.preload.ts b/ts/types/Stickers.preload.ts index 704e964b4e..38d27c6140 100644 --- a/ts/types/Stickers.preload.ts +++ b/ts/types/Stickers.preload.ts @@ -101,6 +101,8 @@ export type StickerPackPointerType = Readonly<{ key: string; }>; +export type StickerManagerTabType = 'all' | 'my-stickers'; + export const STICKERPACK_ID_BYTE_LEN = 16; export const STICKERPACK_KEY_BYTE_LEN = 32; @@ -193,6 +195,7 @@ export async function load(): Promise { recentStickers, blessedPacks, installedPack: null, + stickerManagerTab: 'all', }; packsToDownload = capturePacksToDownload(packs); diff --git a/ts/windows/main/preload_test.preload.ts b/ts/windows/main/preload_test.preload.ts index fc428fbbd9..7b593c5352 100644 --- a/ts/windows/main/preload_test.preload.ts +++ b/ts/windows/main/preload_test.preload.ts @@ -141,6 +141,7 @@ window.testUtilities = { packs: {}, recentStickers: [], blessedPacks: {}, + stickerManagerTab: 'all', }, theme: ThemeType.dark, });