From 796b642f0be41d60df2b301a6e55738fd23e908f Mon Sep 17 00:00:00 2001 From: Arjita Mitra Date: Fri, 24 Apr 2026 16:33:08 +0200 Subject: [PATCH 1/7] feat: Initial UI for search view --- ...rumbs.styles.ts => Conversation.styles.ts} | 32 ++++- .../components/Conversation/Conversation.tsx | 41 +++++- .../CellsHeader/CellsHeader.styles.ts | 61 ++++++-- .../CellsHeader/CellsHeader.tsx | 133 ++++++++---------- .../ConversationCells/ConversationCells.tsx | 25 +++- .../CellsBreadcrumbs/CellsBreadcrumbs.tsx | 9 +- .../script/components/TitleBar/TitleBar.tsx | 111 +++++++++------ .../style/content/conversation/title-bar.less | 20 +++ 8 files changed, 282 insertions(+), 150 deletions(-) rename apps/webapp/src/script/components/Conversation/{ConversationCells/common/CellsBreadcrumbs/CellsBreadcrumbs.styles.ts => Conversation.styles.ts} (50%) diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/common/CellsBreadcrumbs/CellsBreadcrumbs.styles.ts b/apps/webapp/src/script/components/Conversation/Conversation.styles.ts similarity index 50% rename from apps/webapp/src/script/components/Conversation/ConversationCells/common/CellsBreadcrumbs/CellsBreadcrumbs.styles.ts rename to apps/webapp/src/script/components/Conversation/Conversation.styles.ts index 9d0147c3a4d..7e119c5a24a 100644 --- a/apps/webapp/src/script/components/Conversation/ConversationCells/common/CellsBreadcrumbs/CellsBreadcrumbs.styles.ts +++ b/apps/webapp/src/script/components/Conversation/Conversation.styles.ts @@ -19,6 +19,34 @@ import {CSSObject} from '@emotion/react'; -export const wrapperStyles: CSSObject = { - padding: '0 8px', +export const tabsWrapperStyles: CSSObject = { + position: 'relative', +}; + +export const tabsHiddenStyles: CSSObject = { + visibility: 'hidden', +}; + +export const searchResultsOverlayStyles: CSSObject = { + position: 'absolute', + top: 0, + right: 0, + bottom: 0, + left: 0, + display: 'flex', + alignItems: 'flex-end', + paddingLeft: '16px', + paddingBottom: '8px', +}; + +export const searchResultsHeadingStyles: CSSObject = { + margin: 0, + fontSize: 'var(--font-size-medium)', + fontStyle: 'normal', + fontWeight: 'var(--font-weight-semibold)', + lineHeight: 'var(--line-height-md)', + color: 'var(--Backgrounds-On-Background-Variant, #000)', + 'body.theme-dark &': { + color: 'var(--Backgrounds-On-Background-Variant, #FFF)', + }, }; diff --git a/apps/webapp/src/script/components/Conversation/Conversation.tsx b/apps/webapp/src/script/components/Conversation/Conversation.tsx index 8b4549efd6d..b7065224051 100644 --- a/apps/webapp/src/script/components/Conversation/Conversation.tsx +++ b/apps/webapp/src/script/components/Conversation/Conversation.tsx @@ -55,6 +55,12 @@ import {getLogger} from 'Util/logger'; import {safeMailOpen, safeWindowOpen} from 'Util/sanitizationUtil'; import {formatBytes} from 'Util/util'; +import { + searchResultsHeadingStyles, + searchResultsOverlayStyles, + tabsHiddenStyles, + tabsWrapperStyles, +} from './Conversation.styles'; import {ConversationCells} from './ConversationCells/ConversationCells'; import {ConversationFileDropzone} from './ConversationFileDropzone/ConversationFileDropzone'; import {ConversationMessagesWrapper} from './ConversationMessagesWrapper/ConversationMessagesWrapper'; @@ -102,6 +108,7 @@ export const Conversation = ({ const [isConversationLoaded, setIsConversationLoaded] = useState(false); const [inputValue, setInputValue] = useState(''); const [isGiphyModalOpen, setIsGiphyModalOpen] = useState(false); + const [isSharedDriveSearchViewOpen, setIsSharedDriveSearchViewOpen] = useState(false); const conversationState = container.resolve(ConversationState); const callState = container.resolve(CallState); @@ -521,6 +528,12 @@ export const Conversation = ({ const isCellsEnabled = Config.getConfig().FEATURE.ENABLE_CELLS && activeConversation?.cellsState() !== CONVERSATION_CELLS_STATE.DISABLED; + useEffect(() => { + if (!isFileTabActive && isSharedDriveSearchViewOpen) { + setIsSharedDriveSearchViewOpen(false); + } + }, [isFileTabActive, isSharedDriveSearchViewOpen]); + const {getRootProps, getInputProps, openAllFilesView, openImageFilesView, handlePastedFile, isDragAccept} = useFilesUploadDropzone({ isTeam: inTeam, @@ -551,16 +564,30 @@ export const Conversation = ({ openRightSidebar={openRightSidebar} isRightSidebarOpen={isRightSidebarOpen} isReadOnlyConversation={isReadOnlyConversation || isSelfUserRemoved} - withBottomDivider={!isCellsEnabled} + withBottomDivider={!isCellsEnabled || isSharedDriveSearchViewOpen} + isSharedDriveSearchViewOpen={isSharedDriveSearchViewOpen} + onCloseSharedDriveSearchView={() => setIsSharedDriveSearchViewOpen(false)} /> {isCellsEnabled && ( <> - +
+
+ +
+ {isSharedDriveSearchViewOpen && ( +
+

Search results

+
+ )} +
{isFileTabActive && ( setIsSharedDriveSearchViewOpen(true)} /> )} diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.styles.ts b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.styles.ts index c0da59feabf..341006e18a4 100644 --- a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.styles.ts +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.styles.ts @@ -24,8 +24,9 @@ export const wrapperStyles: CSSObject = { justifyContent: 'space-between', alignItems: 'flex-start', flexDirection: 'column', + gap: '16px', marginBottom: '20px', - paddingRight: '8px', + padding: '0 16px', width: '100%', }; @@ -35,6 +36,15 @@ export const contentStyles: CSSObject = { justifyContent: 'space-between', gap: '8px', width: '100%', + minHeight: '32px', +}; + +export const breadcrumbsRowStyles: CSSObject = { + display: 'flex', + width: '100%', + height: '24px', + alignItems: 'center', + gap: '10px', }; export const actionsStyles: CSSObject = { @@ -44,21 +54,48 @@ export const actionsStyles: CSSObject = { }; export const searchInputStyles: CSSObject = { - boxShadow: '0 0 0 0.667px transparent', - '.wireinput': { - padding: 0, - paddingLeft: '8px', - boxShadow: 'none', - '&:focus, &:hover, &:active': { - boxShadow: 'none', - }, - }, + display: 'flex', + width: '394px', + height: '32px', + padding: '7px 12px', + justifyContent: 'space-between', + alignItems: 'center', + flexShrink: 0, + gap: '8px', + borderRadius: '12px', + border: '1px solid var(--Border-Base-Primary, #DCE0E3)', + background: 'var(--Background-Base-Primary, #FFF)', + boxSizing: 'border-box', '&:focus-within': { - boxShadow: '0 0 0 0.667px var(--Light-UI-Blue, #0667C8)', + border: '1px solid var(--Border-Accent-Color-Primary, #0667C8)', }, 'body.theme-dark &': { + border: '1px solid var(--Border-Base-Primary, #34373D)', + background: 'var(--Background-Base-Primary, #17181A)', '&:focus-within': { - boxShadow: '0 0 0 0.667px var(--Dark-UI-Blue, #54a6ff)', + border: '1px solid var(--Border-Accent-Color-Primary, #54A6FF)', }, }, }; + +export const searchIconStyles: CSSObject = { + flexShrink: 0, + width: '11.706px', + height: '12px', +}; + +export const searchNativeInputStyles: CSSObject = { + flex: 1, + minWidth: 0, + border: 'none', + outline: 'none', + background: 'transparent', + fontSize: '14px', + color: 'inherit', + boxShadow: 'none', + padding: 0, + '&:hover, &:focus, &:active': { + boxShadow: 'none', + outline: 'none', + }, +}; diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.tsx b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.tsx index 63ecee9033f..22a2a075e79 100644 --- a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.tsx +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.tsx @@ -17,16 +17,22 @@ * */ -import {useEffect, useRef} from 'react'; - import {QualifiedId} from '@wireapp/api-client/lib/user/'; -import {CloseIcon, Input, InputSubmitCombo, SearchIcon} from '@wireapp/react-ui-kit'; +import {CloseIcon, SearchIcon} from '@wireapp/react-ui-kit'; import {CellsRepository} from 'Repositories/cells/cellsRepository'; import {t} from 'Util/localizerUtil'; -import {actionsStyles, contentStyles, searchInputStyles, wrapperStyles} from './CellsHeader.styles'; +import { + actionsStyles, + breadcrumbsRowStyles, + contentStyles, + searchIconStyles, + searchInputStyles, + searchNativeInputStyles, + wrapperStyles, +} from './CellsHeader.styles'; import {CellsMoreMenu} from './CellsMoreMenu/CellsMoreMenu'; import {CellsNewMenu} from './CellsNewMenu/CellsNewMenu'; import {CellsRefresh} from './CellsRefresh/CellsRefresh'; @@ -41,6 +47,8 @@ interface CellsHeaderProps { conversationName: string; conversationQualifiedId: QualifiedId; cellsRepository: CellsRepository; + isSearchViewOpen: boolean; + onOpenSearchView: () => void; searchValue: string; onSearchChange: (value: string) => void; onSearchClear: () => void; @@ -48,94 +56,71 @@ interface CellsHeaderProps { export const CellsHeader = ({ onRefresh, - conversationQualifiedId, conversationName, + conversationQualifiedId, cellsRepository, + isSearchViewOpen, + onOpenSearchView, searchValue, onSearchChange, onSearchClear, }: CellsHeaderProps) => { - const inputRef = useRef(null); - const breadcrumbs = getBreadcrumbsFromPath({ baseCrumb: t('cells.breadcrumb.files', {conversationName}), currentPath: getCellsFilesPath(), }); - useEffect(() => { - inputRef.current?.focus(); - }, []); - return (
- - openBreadcrumb({ - conversationQualifiedId, - path: breadcrumbs.find(crumb => crumb.name === item.name)?.path ?? '', - }) - } - /> -
- + + + onSearchChange(event.currentTarget.value)} + data-uie-name="full-search-header-input" /> - - + + {searchValue && ( + + )}
+ {!isSearchViewOpen && ( +
+ + + +
+ )}
- - - - div': {width: '100%'}, - input: { - fontSize: '14px', - height: '32px', - '&:hover': { - boxShadow: 'none', - }, - '&:focus': { - outline: 'none', - boxShadow: 'none', - }, - }, - }} - type="text" - value={searchValue} - ref={inputRef} - aria-label={t('cells.search.placeholder')} - placeholder={t('cells.search.placeholder')} - onChange={event => onSearchChange(event.currentTarget.value)} - data-uie-name="full-search-header-input" - /> - - {searchValue && ( - + + openBreadcrumb({ + conversationQualifiedId, + path: breadcrumbs.find(crumb => crumb.name === item.name)?.path ?? '', + }) + } /> - )} - +
+ )} ); }; diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.tsx b/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.tsx index 57a9254873e..66acab3cf91 100644 --- a/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.tsx +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.tsx @@ -17,7 +17,7 @@ * */ -import {memo} from 'react'; +import {memo, useEffect, useRef} from 'react'; import {CONVERSATION_CELLS_STATE} from '@wireapp/api-client/lib/conversation'; @@ -47,10 +47,19 @@ interface ConversationCellsProps { userRepository: UserRepository; activeConversation: Conversation; conversationRepository: ConversationRepository; + isSearchViewOpen: boolean; + onOpenSearchView: () => void; } export const ConversationCells = memo( - ({cellsRepository, userRepository, activeConversation, conversationRepository}: ConversationCellsProps) => { + ({ + cellsRepository, + userRepository, + activeConversation, + conversationRepository, + isSearchViewOpen, + onOpenSearchView, + }: ConversationCellsProps) => { const {cellsState: initialCellState, name} = useKoSubscribableChildren(activeConversation, ['cellsState', 'name']); const {getNodes, status: nodesStatus, getPagination} = useCellsStore(); @@ -87,12 +96,20 @@ export const ConversationCells = memo( }); const isSearchActive = !!searchValue; + const wasSearchViewOpen = useRef(isSearchViewOpen); const handleClearSearch = () => { clearSearch(); void refresh(); }; + useEffect(() => { + if (wasSearchViewOpen.current && !isSearchViewOpen && searchValue) { + handleClearSearch(); + } + wasSearchViewOpen.current = isSearchViewOpen; + }, [isSearchViewOpen, searchValue]); + // When search is active, refresh should trigger search reload const handleRefresh = isSearchActive ? () => handleSearch(searchValue) : refresh; @@ -125,9 +142,11 @@ export const ConversationCells = memo(
; - onItemClick: (item: {name: string}) => void; } export const CellsBreadcrumbs = ({maxNotCombinedItems, items, onItemClick}: CellsBreadcrumbsProps) => { - return ( -
- -
- ); + return ; }; diff --git a/apps/webapp/src/script/components/TitleBar/TitleBar.tsx b/apps/webapp/src/script/components/TitleBar/TitleBar.tsx index e4a1e02b6d9..2da45f4899c 100644 --- a/apps/webapp/src/script/components/TitleBar/TitleBar.tsx +++ b/apps/webapp/src/script/components/TitleBar/TitleBar.tsx @@ -61,6 +61,8 @@ interface TitleBarProps { callState?: CallState; isReadOnlyConversation?: boolean; withBottomDivider: boolean; + isSharedDriveSearchViewOpen?: boolean; + onCloseSharedDriveSearchView?: () => void; } export const TitleBar = ({ @@ -74,6 +76,8 @@ export const TitleBar = ({ teamState = container.resolve(TeamState), isReadOnlyConversation = false, withBottomDivider, + isSharedDriveSearchViewOpen = false, + onCloseSharedDriveSearchView, }: TitleBarProps) => { const { is1to1, @@ -266,7 +270,20 @@ export const TitleBar = ({ /> )} - {isActivatedAccount && !mdBreakpoint && ( + {isSharedDriveSearchViewOpen && ( + + )} + + {isActivatedAccount && !mdBreakpoint && !isSharedDriveSearchViewOpen && ( - )} - - {mdBreakpoint ? ( + {!isSharedDriveSearchViewOpen && ( <> - - {t('tooltipConversationSearch')} - - {showCallControls && ( - { + currentFocusedElementRef.current = event.target as HTMLButtonElement; + startCallAndShowAlert(); + }} data-uie-name="do-call" disabled={isCallButtonDisabled} > - + + )} + + {mdBreakpoint ? ( + <> + + {t('tooltipConversationSearch')} + + {showCallControls && ( + + + + )} + + ) : ( + )} - ) : ( - )} diff --git a/apps/webapp/src/style/content/conversation/title-bar.less b/apps/webapp/src/style/content/conversation/title-bar.less index 0345649ae1f..16c721514de 100644 --- a/apps/webapp/src/style/content/conversation/title-bar.less +++ b/apps/webapp/src/style/content/conversation/title-bar.less @@ -100,6 +100,26 @@ body.theme-dark { * > { display: flex; } + + &--borderless { + border: 1px solid transparent; + background-color: transparent; + + &:hover { + border-color: transparent; + background-color: transparent; + } + + body.theme-dark & { + border: 1px solid transparent; + background-color: transparent; + + &:hover { + border-color: transparent; + background-color: transparent; + } + } + } } .conversation-title-bar-name-label { From ad5943bc3c6809e6625ecce164154d108c203a3c Mon Sep 17 00:00:00 2001 From: Arjita Mitra Date: Thu, 30 Apr 2026 14:50:43 +0200 Subject: [PATCH 2/7] feat: home icon in breadcrumbs when user is at root level(WPB-25163) --- .../CellsHeader/CellsHeader.styles.ts | 6 +++ .../CellsHeader/CellsHeader.tsx | 24 ++++++---- .../CellsHeader/CellsRootHomeIcon.tsx | 48 +++++++++++++++++++ 3 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsRootHomeIcon.tsx diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.styles.ts b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.styles.ts index 341006e18a4..2f2fbbdac46 100644 --- a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.styles.ts +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.styles.ts @@ -47,6 +47,12 @@ export const breadcrumbsRowStyles: CSSObject = { gap: '10px', }; +export const rootHomeIconStyles: CSSObject = { + width: '14px', + height: '14px', + flexShrink: 0, +}; + export const actionsStyles: CSSObject = { display: 'flex', alignItems: 'center', diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.tsx b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.tsx index 22a2a075e79..5ba57c5fa70 100644 --- a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.tsx +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.tsx @@ -36,6 +36,7 @@ import { import {CellsMoreMenu} from './CellsMoreMenu/CellsMoreMenu'; import {CellsNewMenu} from './CellsNewMenu/CellsNewMenu'; import {CellsRefresh} from './CellsRefresh/CellsRefresh'; +import {CellsRootHomeIcon} from './CellsRootHomeIcon'; import {CellsBreadcrumbs} from '../common/CellsBreadcrumbs/CellsBreadcrumbs'; import {getBreadcrumbsFromPath} from '../common/getBreadcrumbsFromPath/getBreadcrumbsFromPath'; @@ -69,6 +70,7 @@ export const CellsHeader = ({ baseCrumb: t('cells.breadcrumb.files', {conversationName}), currentPath: getCellsFilesPath(), }); + const isRootLevel = breadcrumbs.length === 1; return (
@@ -110,15 +112,19 @@ export const CellsHeader = ({
{!isSearchViewOpen && (
- - openBreadcrumb({ - conversationQualifiedId, - path: breadcrumbs.find(crumb => crumb.name === item.name)?.path ?? '', - }) - } - /> + {isRootLevel ? ( + + ) : ( + + openBreadcrumb({ + conversationQualifiedId, + path: breadcrumbs.find(crumb => crumb.name === item.name)?.path ?? '', + }) + } + /> + )}
)}
diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsRootHomeIcon.tsx b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsRootHomeIcon.tsx new file mode 100644 index 00000000000..39dd194ff8a --- /dev/null +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsRootHomeIcon.tsx @@ -0,0 +1,48 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {rootHomeIconStyles} from './CellsHeader.styles'; + +export const CellsRootHomeIcon = () => { + return ( + + ); +}; From 7ae7a3568b9cb68ce52cd5236345d8f821f91d02 Mon Sep 17 00:00:00 2001 From: Arjita Mitra Date: Thu, 30 Apr 2026 16:01:08 +0200 Subject: [PATCH 3/7] feat: show default search view message, update clear icon --- apps/webapp/src/i18n/en-US.json | 2 ++ .../CellsHeader/CellsHeader.styles.ts | 20 +++++++++++ .../CellsHeader/CellsHeader.tsx | 13 +++++--- .../CellsHeader/CellsSearchClearIcon.tsx | 33 +++++++++++++++++++ .../ConversationCells.styles.ts | 31 +++++++++++++++++ .../ConversationCells/ConversationCells.tsx | 29 +++++++++++----- apps/webapp/src/types/i18n.d.ts | 2 ++ 7 files changed, 118 insertions(+), 12 deletions(-) create mode 100644 apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsSearchClearIcon.tsx diff --git a/apps/webapp/src/i18n/en-US.json b/apps/webapp/src/i18n/en-US.json index e850c09e2b8..0a4d82e3753 100644 --- a/apps/webapp/src/i18n/en-US.json +++ b/apps/webapp/src/i18n/en-US.json @@ -480,6 +480,8 @@ "cells.restoreRootNodeModal.folder.description": "This folder {name} will be restored with all its contents and available again to everyone in this conversation.", "cells.restoreRootNodeModal.folder.headline": "Restore folder", "cells.search.closeButton": "Close", + "cells.search.idle.description": "Apply a search terms, or a filter to see results.", + "cells.search.idle.heading": "Searching within Shared Drive and all its folders", "cells.search.failed": "Something went wrong, please try again later.", "cells.search.placeholder": "Search files and folders", "cells.selfDeletingMessage.info": "The feature is not available for conversations with a shared Drive.", diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.styles.ts b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.styles.ts index 2f2fbbdac46..ac21c68d3b0 100644 --- a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.styles.ts +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.styles.ts @@ -105,3 +105,23 @@ export const searchNativeInputStyles: CSSObject = { outline: 'none', }, }; + +export const clearButtonStyles: CSSObject = { + border: 'none', + background: 'transparent', + padding: 0, + margin: 0, + lineHeight: 0, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + cursor: 'pointer', + color: 'var(--main-color)', + flexShrink: 0, +}; + +export const clearIconStyles: CSSObject = { + width: '16px', + height: '16px', + flexShrink: 0, +}; diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.tsx b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.tsx index 5ba57c5fa70..0d253f2cb31 100644 --- a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.tsx +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsHeader.tsx @@ -19,7 +19,7 @@ import {QualifiedId} from '@wireapp/api-client/lib/user/'; -import {CloseIcon, SearchIcon} from '@wireapp/react-ui-kit'; +import {SearchIcon} from '@wireapp/react-ui-kit'; import {CellsRepository} from 'Repositories/cells/cellsRepository'; import {t} from 'Util/localizerUtil'; @@ -27,6 +27,7 @@ import {t} from 'Util/localizerUtil'; import { actionsStyles, breadcrumbsRowStyles, + clearButtonStyles, contentStyles, searchIconStyles, searchInputStyles, @@ -36,6 +37,7 @@ import { import {CellsMoreMenu} from './CellsMoreMenu/CellsMoreMenu'; import {CellsNewMenu} from './CellsNewMenu/CellsNewMenu'; import {CellsRefresh} from './CellsRefresh/CellsRefresh'; +import {CellsSearchClearIcon} from './CellsSearchClearIcon'; import {CellsRootHomeIcon} from './CellsRootHomeIcon'; import {CellsBreadcrumbs} from '../common/CellsBreadcrumbs/CellsBreadcrumbs'; @@ -90,12 +92,15 @@ export const CellsHeader = ({ /> {searchValue && ( - + > + + )} {!isSearchViewOpen && ( diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsSearchClearIcon.tsx b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsSearchClearIcon.tsx new file mode 100644 index 00000000000..03ea5ed2649 --- /dev/null +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/CellsHeader/CellsSearchClearIcon.tsx @@ -0,0 +1,33 @@ +/* + * Wire + * Copyright (C) 2026 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + * + */ + +import {clearIconStyles} from './CellsHeader.styles'; + +export const CellsSearchClearIcon = () => { + return ( + + ); +}; diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.styles.ts b/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.styles.ts index 19bcee29d90..be90b9a1475 100644 --- a/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.styles.ts +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.styles.ts @@ -25,3 +25,34 @@ export const wrapperStyles: CSSObject = { flexDirection: 'column', height: '100%', }; + +export const searchIdleStateStyles: CSSObject = { + display: 'flex', + width: '405px', + flexDirection: 'column', + alignItems: 'center', + gap: '16px', + margin: '64px auto 0', +}; + +export const searchIdleHeadingStyles: CSSObject = { + color: 'var(--main-color)', + textAlign: 'center', + fontSize: 'var(--font-size-base, 16px)', + fontStyle: 'normal', + fontWeight: 'var(--font-weight-bold, 700)', + lineHeight: 'var(--line-height-lg, 24px)', + letterSpacing: '0.05px', + margin: 0, +}; + +export const searchIdleDescriptionStyles: CSSObject = { + color: 'var(--main-color)', + textAlign: 'center', + fontSize: 'var(--font-size-base, 16px)', + fontStyle: 'normal', + fontWeight: 'var(--font-weight-regular, 400)', + lineHeight: 'var(--line-height-lg, 24px)', + letterSpacing: '0.05px', + margin: 0, +}; diff --git a/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.tsx b/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.tsx index 66acab3cf91..5be790cf418 100644 --- a/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.tsx +++ b/apps/webapp/src/script/components/Conversation/ConversationCells/ConversationCells.tsx @@ -35,7 +35,12 @@ import {CellsStateInfo} from './CellsStateInfo/CellsStateInfo'; import {CellsTable} from './CellsTable/CellsTable'; import {isInRecycleBin} from './common/recycleBin/recycleBin'; import {useCellsStore} from './common/useCellsStore/useCellsStore'; -import {wrapperStyles} from './ConversationCells.styles'; +import { + searchIdleDescriptionStyles, + searchIdleHeadingStyles, + searchIdleStateStyles, + wrapperStyles, +} from './ConversationCells.styles'; import {useCellsPagination} from './useCellsPagination/useCellsPagination'; import {useConversationSearchFiles} from './useConversationSearch/useConversationSearchFiles'; import {useGetAllCellsNodes} from './useGetAllCellsNodes/useGetAllCellsNodes'; @@ -95,7 +100,9 @@ export const ConversationCells = memo( onClear: refresh, }); - const isSearchActive = !!searchValue; + const trimmedSearchValue = searchValue.trim(); + const isSearchActive = !!trimmedSearchValue; + const isSearchViewIdle = isSearchViewOpen && !trimmedSearchValue; const wasSearchViewOpen = useRef(isSearchViewOpen); const handleClearSearch = () => { @@ -132,11 +139,11 @@ export const ConversationCells = memo( const hasNodes = !!nodes.length; const emptyView = !isError && !hasNodes && isCellsStateReady; - const isTableVisible = (isSuccess || isLoading) && isCellsStateReady; - const isLoadingVisible = isLoading && isCellsStateReady; - const isNoNodesVisible = !isLoading && emptyView && !isInRecycleBin(); - const isPaginationVisible = !emptyView; - const isEmptyRecycleBin = isInRecycleBin() && emptyView && !isLoading; + const isTableVisible = (isSuccess || isLoading) && isCellsStateReady && !isSearchViewIdle; + const isLoadingVisible = isLoading && isCellsStateReady && !isSearchViewIdle; + const isNoNodesVisible = !isLoading && emptyView && !isInRecycleBin() && !isSearchViewIdle; + const isPaginationVisible = !emptyView && !isSearchViewIdle; + const isEmptyRecycleBin = isInRecycleBin() && emptyView && !isLoading && !isSearchViewIdle; return (
@@ -160,6 +167,12 @@ export const ConversationCells = memo( onRefresh={handleRefresh} /> )} + {isSearchViewIdle && ( +
+

{t('cells.search.idle.heading')}

+

{t('cells.search.idle.description')}

+
+ )} {isCellsStatePending && !isRefreshing && ( )} @@ -167,7 +180,7 @@ export const ConversationCells = memo( )} {isEmptyRecycleBin && } - {(isLoadingVisible || isRefreshing) && } + {(isLoadingVisible || (isRefreshing && !isSearchViewIdle)) && } {isError && } {isPaginationVisible && }
diff --git a/apps/webapp/src/types/i18n.d.ts b/apps/webapp/src/types/i18n.d.ts index 65716ca80cc..b9d484a5f02 100644 --- a/apps/webapp/src/types/i18n.d.ts +++ b/apps/webapp/src/types/i18n.d.ts @@ -484,6 +484,8 @@ declare module 'I18n/en-US.json' { 'cells.restoreRootNodeModal.folder.description': `This folder {name} will be restored with all its contents and available again to everyone in this conversation.`; 'cells.restoreRootNodeModal.folder.headline': `Restore folder`; 'cells.search.closeButton': `Close`; + 'cells.search.idle.description': `Apply a search terms, or a filter to see results.`; + 'cells.search.idle.heading': `Searching within Shared Drive and all its folders`; 'cells.search.failed': `Something went wrong, please try again later.`; 'cells.search.placeholder': `Search files and folders`; 'cells.selfDeletingMessage.info': `The feature is not available for conversations with a shared Drive.`; From 6c07629f69f28dc175aecfc5d7a6a3a5f6b3b95c Mon Sep 17 00:00:00 2001 From: Arjita Mitra Date: Mon, 4 May 2026 09:46:21 +0200 Subject: [PATCH 4/7] fix: review comments --- apps/webapp/src/script/components/Conversation/Conversation.tsx | 2 +- apps/webapp/src/script/components/TitleBar/TitleBar.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/webapp/src/script/components/Conversation/Conversation.tsx b/apps/webapp/src/script/components/Conversation/Conversation.tsx index b7065224051..86222d5e6c8 100644 --- a/apps/webapp/src/script/components/Conversation/Conversation.tsx +++ b/apps/webapp/src/script/components/Conversation/Conversation.tsx @@ -574,7 +574,7 @@ export const Conversation = ({
{ - currentFocusedElementRef.current = event.target as HTMLButtonElement; + currentFocusedElementRef.current = event.currentTarget; startCallAndShowAlert(); }} data-uie-name="do-call" From 17a39ccde47be213065aee6808e1418da2629f5b Mon Sep 17 00:00:00 2001 From: Arjita Mitra Date: Mon, 4 May 2026 10:00:16 +0200 Subject: [PATCH 5/7] fix: translate merge --- apps/webapp/src/i18n/en-US.json | 2 +- apps/webapp/src/types/i18n.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/webapp/src/i18n/en-US.json b/apps/webapp/src/i18n/en-US.json index 0a4d82e3753..8cc7402b163 100644 --- a/apps/webapp/src/i18n/en-US.json +++ b/apps/webapp/src/i18n/en-US.json @@ -480,9 +480,9 @@ "cells.restoreRootNodeModal.folder.description": "This folder {name} will be restored with all its contents and available again to everyone in this conversation.", "cells.restoreRootNodeModal.folder.headline": "Restore folder", "cells.search.closeButton": "Close", + "cells.search.failed": "Something went wrong, please try again later.", "cells.search.idle.description": "Apply a search terms, or a filter to see results.", "cells.search.idle.heading": "Searching within Shared Drive and all its folders", - "cells.search.failed": "Something went wrong, please try again later.", "cells.search.placeholder": "Search files and folders", "cells.selfDeletingMessage.info": "The feature is not available for conversations with a shared Drive.", "cells.shareModal.changePassword": "Change Password", diff --git a/apps/webapp/src/types/i18n.d.ts b/apps/webapp/src/types/i18n.d.ts index b9d484a5f02..bcbaad6c630 100644 --- a/apps/webapp/src/types/i18n.d.ts +++ b/apps/webapp/src/types/i18n.d.ts @@ -484,9 +484,9 @@ declare module 'I18n/en-US.json' { 'cells.restoreRootNodeModal.folder.description': `This folder {name} will be restored with all its contents and available again to everyone in this conversation.`; 'cells.restoreRootNodeModal.folder.headline': `Restore folder`; 'cells.search.closeButton': `Close`; + 'cells.search.failed': `Something went wrong, please try again later.`; 'cells.search.idle.description': `Apply a search terms, or a filter to see results.`; 'cells.search.idle.heading': `Searching within Shared Drive and all its folders`; - 'cells.search.failed': `Something went wrong, please try again later.`; 'cells.search.placeholder': `Search files and folders`; 'cells.selfDeletingMessage.info': `The feature is not available for conversations with a shared Drive.`; 'cells.shareModal.changePassword': `Change Password`; From 7ed490b80c557db681b490b6913c601bb86d71a1 Mon Sep 17 00:00:00 2001 From: Arjita Mitra Date: Mon, 4 May 2026 12:43:59 +0200 Subject: [PATCH 6/7] fix: pipeline import order issue --- apps/webapp/src/script/components/Conversation/Conversation.tsx | 2 +- .../Conversation/ConversationCells/CellsHeader/CellsHeader.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/webapp/src/script/components/Conversation/Conversation.tsx b/apps/webapp/src/script/components/Conversation/Conversation.tsx index 86222d5e6c8..b7065224051 100644 --- a/apps/webapp/src/script/components/Conversation/Conversation.tsx +++ b/apps/webapp/src/script/components/Conversation/Conversation.tsx @@ -574,7 +574,7 @@ export const Conversation = ({
Date: Mon, 4 May 2026 13:07:54 +0200 Subject: [PATCH 7/7] feat: add startup feature toggle for shared drive search,filter --- .../components/Conversation/Conversation.tsx | 29 ++++++++++++------- .../startupFeatureToggleNames.ts | 2 ++ .../startupFeatureToggles.test.ts | 11 +++++++ 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/apps/webapp/src/script/components/Conversation/Conversation.tsx b/apps/webapp/src/script/components/Conversation/Conversation.tsx index b7065224051..00e5895818e 100644 --- a/apps/webapp/src/script/components/Conversation/Conversation.tsx +++ b/apps/webapp/src/script/components/Conversation/Conversation.tsx @@ -46,6 +46,7 @@ import {User} from 'Repositories/entity/User'; import {ServiceEntity} from 'Repositories/integration/ServiceEntity'; import {TeamState} from 'Repositories/team/TeamState'; import {Config} from 'src/script/Config'; +import {sharedDriveSearchAndFiltersFeatureToggleName} from 'src/script/featureToggles/startupFeatureToggleNames'; import {useKoSubscribableChildren} from 'Util/componentUtil'; import {isLastReceivedMessage} from 'Util/conversationMessages'; import {allowsAllFiles, getFileExtensionOrName, hasAllowedExtension} from 'Util/fileTypeUtil'; @@ -77,7 +78,7 @@ import {isServiceEntity} from '../../guards/Service'; import {MotionDuration} from '../../motion/MotionDuration'; import {RightSidebarParams} from '../../page/AppMain'; import {PanelState} from '../../page/RightSidebar'; -import {useMainViewModel} from '../../page/RootProvider'; +import {useApplicationContext, useMainViewModel} from '../../page/RootProvider'; import {ElementType, MessageDetails} from '../MessagesList/Message/ContentMessage/asset/TextMessageRenderer'; interface ConversationProps { @@ -102,8 +103,10 @@ export const Conversation = ({ const isVirtualizedMessagesListEnabled = CONFIG.FEATURE.ENABLE_VIRTUALIZED_MESSAGES_LIST; const mainViewModel = useMainViewModel(); + const {isFeatureToggleEnabled} = useApplicationContext(); const {content: contentViewModel} = mainViewModel; const {conversationRepository, repositories} = contentViewModel; + const isSharedDriveSearchAndFiltersEnabled = isFeatureToggleEnabled(sharedDriveSearchAndFiltersFeatureToggleName); const [isConversationLoaded, setIsConversationLoaded] = useState(false); const [inputValue, setInputValue] = useState(''); @@ -529,10 +532,10 @@ export const Conversation = ({ Config.getConfig().FEATURE.ENABLE_CELLS && activeConversation?.cellsState() !== CONVERSATION_CELLS_STATE.DISABLED; useEffect(() => { - if (!isFileTabActive && isSharedDriveSearchViewOpen) { + if ((!isFileTabActive || !isSharedDriveSearchAndFiltersEnabled) && isSharedDriveSearchViewOpen) { setIsSharedDriveSearchViewOpen(false); } - }, [isFileTabActive, isSharedDriveSearchViewOpen]); + }, [isFileTabActive, isSharedDriveSearchAndFiltersEnabled, isSharedDriveSearchViewOpen]); const {getRootProps, getInputProps, openAllFilesView, openImageFilesView, handlePastedFile, isDragAccept} = useFilesUploadDropzone({ @@ -564,8 +567,8 @@ export const Conversation = ({ openRightSidebar={openRightSidebar} isRightSidebarOpen={isRightSidebarOpen} isReadOnlyConversation={isReadOnlyConversation || isSelfUserRemoved} - withBottomDivider={!isCellsEnabled || isSharedDriveSearchViewOpen} - isSharedDriveSearchViewOpen={isSharedDriveSearchViewOpen} + withBottomDivider={!isCellsEnabled || (isSharedDriveSearchAndFiltersEnabled && isSharedDriveSearchViewOpen)} + isSharedDriveSearchViewOpen={isSharedDriveSearchAndFiltersEnabled && isSharedDriveSearchViewOpen} onCloseSharedDriveSearchView={() => setIsSharedDriveSearchViewOpen(false)} /> @@ -573,8 +576,10 @@ export const Conversation = ({ <>
- {isSharedDriveSearchViewOpen && ( + {isSharedDriveSearchAndFiltersEnabled && isSharedDriveSearchViewOpen && (

Search results

@@ -595,8 +600,12 @@ export const Conversation = ({ userRepository={repositories.user} cellsRepository={repositories.cells} conversationRepository={conversationRepository} - isSearchViewOpen={isSharedDriveSearchViewOpen} - onOpenSearchView={() => setIsSharedDriveSearchViewOpen(true)} + isSearchViewOpen={isSharedDriveSearchAndFiltersEnabled && isSharedDriveSearchViewOpen} + onOpenSearchView={() => { + if (isSharedDriveSearchAndFiltersEnabled) { + setIsSharedDriveSearchViewOpen(true); + } + }} /> )} diff --git a/apps/webapp/src/script/featureToggles/startupFeatureToggleNames.ts b/apps/webapp/src/script/featureToggles/startupFeatureToggleNames.ts index bd0115649b5..b848d19b050 100644 --- a/apps/webapp/src/script/featureToggles/startupFeatureToggleNames.ts +++ b/apps/webapp/src/script/featureToggles/startupFeatureToggleNames.ts @@ -19,10 +19,12 @@ export const reliableWebsocketConnectionFeatureToggleName = 'reliable-websocket-connection'; export const applockRefactoredFeatureToggleName = 'applock-refactored'; +export const sharedDriveSearchAndFiltersFeatureToggleName = 'shared-drive-search-and-filters'; export const startupFeatureToggleNames = [ reliableWebsocketConnectionFeatureToggleName, applockRefactoredFeatureToggleName, + sharedDriveSearchAndFiltersFeatureToggleName, ] as const; export type StartupFeatureToggleName = (typeof startupFeatureToggleNames)[number]; diff --git a/apps/webapp/src/script/featureToggles/startupFeatureToggles.test.ts b/apps/webapp/src/script/featureToggles/startupFeatureToggles.test.ts index e467bd9b4d7..d6fd3935f38 100644 --- a/apps/webapp/src/script/featureToggles/startupFeatureToggles.test.ts +++ b/apps/webapp/src/script/featureToggles/startupFeatureToggles.test.ts @@ -25,12 +25,14 @@ import { import { applockRefactoredFeatureToggleName, reliableWebsocketConnectionFeatureToggleName, + sharedDriveSearchAndFiltersFeatureToggleName, startupFeatureToggleNames, } from './startupFeatureToggleNames'; const featureToggleNamesWithDedicatedExistenceTests = [ reliableWebsocketConnectionFeatureToggleName, applockRefactoredFeatureToggleName, + sharedDriveSearchAndFiltersFeatureToggleName, ] as const; describe('startupFeatureToggles', function () { @@ -83,6 +85,14 @@ describe('startupFeatureToggles', function () { expect(startupFeatureToggles.isFeatureToggleEnabled(applockRefactoredFeatureToggleName)).toBe(true); }); + it('enables the shared drive search and filters feature toggle when present in the query parameter', () => { + const startupFeatureToggles = createStartupFeatureTogglesFromLocationSearch( + `?${startupFeatureToggleQueryParameterName}=${sharedDriveSearchAndFiltersFeatureToggleName}`, + ); + + expect(startupFeatureToggles.isFeatureToggleEnabled(sharedDriveSearchAndFiltersFeatureToggleName)).toBe(true); + }); + it('trims whitespace around feature toggle names', () => { const startupFeatureToggles = createStartupFeatureTogglesFromLocationSearch( `?${startupFeatureToggleQueryParameterName}= ${reliableWebsocketConnectionFeatureToggleName} `, @@ -127,6 +137,7 @@ describe('startupFeatureToggles', function () { expect(allowedStartupFeatureToggleNames).toEqual([ reliableWebsocketConnectionFeatureToggleName, applockRefactoredFeatureToggleName, + sharedDriveSearchAndFiltersFeatureToggleName, ]); });