From 94b19393fd9eb0d4fd8c8fe5ccfc1245aaf9bb15 Mon Sep 17 00:00:00 2001 From: Osama Sayed Date: Sat, 21 Dec 2024 17:18:24 +0800 Subject: [PATCH] updates --- locales/en/common.json | 4 +- public/icons/search/arabic.svg | 5 + public/icons/search/ayah-range.svg | 12 ++ public/icons/search/juz.svg | 5 + public/icons/search/page.svg | 5 + public/icons/search/surah.svg | 9 ++ public/icons/search/translation.svg | 8 ++ public/icons/search/transliteration.svg | 5 + .../CommandBarBase/CommandBarBase.module.scss | 50 ++------ .../CommandBarBody/CommandBarBody.module.scss | 15 ++- .../CommandBar/CommandBarBody/index.tsx | 38 +++--- .../CommandBarTrigger.module.scss | 16 +-- .../CommandsList/CommandControl.tsx | 2 - .../CommandsList/CommandList.module.scss | 6 +- .../CommandPrefix/CommandPrefix.module.scss | 5 +- .../CommandsList/CommandPrefix/index.tsx | 25 ++-- .../CommandBar/CommandsList/index.tsx | 35 +++++- src/components/HomePage/HomePageHero.tsx | 2 +- src/components/HomePage/PlayRadioButton.tsx | 3 +- src/components/HomePage/QuickLinks/index.tsx | 3 +- .../SearchDrawer/Footer/Footer.module.scss | 5 +- .../Navbar/SearchDrawer/Footer/index.tsx | 12 +- .../Navbar/SearchDrawer/SearchDrawer.tsx | 1 + .../Search/NavigationItem/index.tsx | 81 ------------- .../PreInput/SearchQuerySuggestion/index.tsx | 7 +- src/components/Search/PreInput/index.tsx | 54 +++++---- src/components/Search/SearchBodyContainer.tsx | 9 +- src/components/Search/SearchHistory/index.tsx | 13 +- .../SearchInput/SearchInput.module.scss | 25 ++++ src/components/Search/SearchInput/index.tsx | 60 ++++++++++ .../SearchResultItem.module.scss | 33 ------ .../Search/SearchResults/SearchResultItem.tsx | 69 ----------- .../SearchResultItem.module.scss | 18 +++ .../index.tsx} | 40 ++++--- .../SearchResultItemIcon/index.tsx | 35 ++++++ .../SearchResults/SearchResults.module.scss | 28 ----- .../SearchResultsHeader.module.scss | 25 ++++ .../SearchResultsHeader/index.tsx | 48 ++++++++ src/components/Search/SearchResults/index.tsx | 107 ++++++----------- .../BodyContainer/SearchResults.tsx | 20 ++-- src/components/dls/Forms/Input/index.tsx | 12 +- src/pages/search.module.scss | 60 ---------- src/pages/search.tsx | 111 +++++------------- src/styles/theme.scss | 1 + src/styles/themes/_dark.scss | 1 + src/styles/themes/_light.scss | 4 +- src/styles/themes/_sepia.scss | 3 +- src/utils/search.ts | 56 ++++++--- types/QueryParam.ts | 2 + types/Search/SearchNavigationResult.ts | 1 + 50 files changed, 571 insertions(+), 623 deletions(-) create mode 100644 public/icons/search/arabic.svg create mode 100644 public/icons/search/ayah-range.svg create mode 100644 public/icons/search/juz.svg create mode 100644 public/icons/search/page.svg create mode 100644 public/icons/search/surah.svg create mode 100644 public/icons/search/translation.svg create mode 100644 public/icons/search/transliteration.svg delete mode 100644 src/components/Search/NavigationItem/index.tsx create mode 100644 src/components/Search/SearchInput/SearchInput.module.scss create mode 100644 src/components/Search/SearchInput/index.tsx delete mode 100644 src/components/Search/SearchResults/SearchResultItem.module.scss delete mode 100644 src/components/Search/SearchResults/SearchResultItem.tsx create mode 100644 src/components/Search/SearchResults/SearchResultItem/SearchResultItem.module.scss rename src/components/Search/SearchResults/{KalimatNavigationSearchResultItem.tsx => SearchResultItem/index.tsx} (53%) create mode 100644 src/components/Search/SearchResults/SearchResultItemIcon/index.tsx delete mode 100644 src/components/Search/SearchResults/SearchResults.module.scss create mode 100644 src/components/Search/SearchResults/SearchResultsHeader/SearchResultsHeader.module.scss create mode 100644 src/components/Search/SearchResults/SearchResultsHeader/index.tsx diff --git a/locales/en/common.json b/locales/en/common.json index c4a8d56e5b..4c9ef4487f 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -253,6 +253,7 @@ "pagination-summary": "{{currentResultNumber}}-{{endOfResultNumber}} of {{totalNumberOfResults}} search results", "pbuh": "Blessings of Allah be upon him", "popular-links": "Popular Links", + "search-results-no-count": "Search Results", "popup": { "footnote": "Monthly donations allow us to focus less on fundraising", "text-1": "We are committed to serving the world Quranic knowledge and technology, always for free.", @@ -303,7 +304,8 @@ "results": "results", "show-all": "Show all results", "switch-mode": "Switch to Advanced Search", - "title": "Search" + "title": "Search", + "more-results": "More results" }, "seconds": "Seconds", "see-new": "See What's New", diff --git a/public/icons/search/arabic.svg b/public/icons/search/arabic.svg new file mode 100644 index 0000000000..ecdc4e375f --- /dev/null +++ b/public/icons/search/arabic.svg @@ -0,0 +1,5 @@ + + + diff --git a/public/icons/search/ayah-range.svg b/public/icons/search/ayah-range.svg new file mode 100644 index 0000000000..18e4ccaf4d --- /dev/null +++ b/public/icons/search/ayah-range.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/public/icons/search/juz.svg b/public/icons/search/juz.svg new file mode 100644 index 0000000000..706b46de0f --- /dev/null +++ b/public/icons/search/juz.svg @@ -0,0 +1,5 @@ + + + diff --git a/public/icons/search/page.svg b/public/icons/search/page.svg new file mode 100644 index 0000000000..9d78539173 --- /dev/null +++ b/public/icons/search/page.svg @@ -0,0 +1,5 @@ + + + diff --git a/public/icons/search/surah.svg b/public/icons/search/surah.svg new file mode 100644 index 0000000000..1be7fb12d1 --- /dev/null +++ b/public/icons/search/surah.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/public/icons/search/translation.svg b/public/icons/search/translation.svg new file mode 100644 index 0000000000..37ed847dbe --- /dev/null +++ b/public/icons/search/translation.svg @@ -0,0 +1,8 @@ + + + + diff --git a/public/icons/search/transliteration.svg b/public/icons/search/transliteration.svg new file mode 100644 index 0000000000..b1c37fec4e --- /dev/null +++ b/public/icons/search/transliteration.svg @@ -0,0 +1,5 @@ + + + diff --git a/src/components/CommandBar/CommandBarBase/CommandBarBase.module.scss b/src/components/CommandBar/CommandBarBase/CommandBarBase.module.scss index d56db90499..bf75ea67bb 100644 --- a/src/components/CommandBar/CommandBarBase/CommandBarBase.module.scss +++ b/src/components/CommandBar/CommandBarBase/CommandBarBase.module.scss @@ -32,18 +32,21 @@ $min-height: calc(9 * var(--spacing-mega)); } } +$start-offset: 25%; +$end-offset: 50%; + .content { - --content-translate-position: translate(-50%, -50%); + --content-translate-position: translate(-#{$end-offset}, -#{$start-offset}); [dir="rtl"] & { - --content-translate-position: translate(50%, -50%); + --content-translate-position: translate(#{$end-offset}, -#{$start-offset}); } background-color: var(--color-background-default); - border-radius: var(--border-radius-rounded); + border-radius: var(--border-radius-circle-small); box-shadow: $shadow; position: fixed; - inset-block-start: 50%; - inset-inline-start: 50%; + inset-block-start: $start-offset; + inset-inline-start: #{$end-offset}; transform: var(--content-translate-position); width: $width; max-width: $max-width; @@ -65,45 +68,8 @@ $min-height: calc(9 * var(--spacing-mega)); z-index: var(--z-index-modal); } -@keyframes overlayShow { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -@keyframes overlayHide { - from { - opacity: 1; - } - to { - opacity: 0; - } -} - .overlay { - @include theme.light { - background-color: $overlay-background-light; - } - @include theme.dark { - background-color: $overlay-background-dark; - } - @include theme.sepia { - background-color: $overlay-background-sepia; - } - backdrop-filter: blur(6px); position: fixed; inset: 0; - - @media (prefers-reduced-motion: no-preference) { - &[data-state="open"] { - animation: overlayShow var(--transition-fast) ease; - } - &[data-state="closed"] { - animation: overlayHide var(--transition-fast) ease; - } - } z-index: var(--z-index-overlay); } diff --git a/src/components/CommandBar/CommandBarBody/CommandBarBody.module.scss b/src/components/CommandBar/CommandBarBody/CommandBarBody.module.scss index e2e9fafeac..2ecb821c0e 100644 --- a/src/components/CommandBar/CommandBarBody/CommandBarBody.module.scss +++ b/src/components/CommandBar/CommandBarBody/CommandBarBody.module.scss @@ -3,7 +3,12 @@ $height: calc(9 * var(--spacing-mega)); .container { padding: 0 var(--spacing-large); + @include breakpoints.smallerThanTablet { + padding: 0 var(--spacing-xxsmall); + } + background: var(--color-background-elevated); box-sizing: border-box; + border-radius: var(--border-radius-circle-small); } .textInputContainer { @@ -23,7 +28,6 @@ $height: calc(9 * var(--spacing-mega)); .inputContainer { border-block-end: 1px solid var(--color-borders-hairline); - padding-block: var(--spacing-small); display: flex; align-items: center; justify-content: space-between; @@ -46,7 +50,7 @@ $height: calc(9 * var(--spacing-mega)); font-size: var(--font-size-xlarge); } padding: var(--spacing-small) 0; - color: var(--color-text-faded); + color: var(--color-text-default); &::placeholder { color: var(--color-text-faded); opacity: var(--opacity-50); @@ -59,14 +63,9 @@ $height: calc(9 * var(--spacing-mega)); padding-block-end: var(--spacing-xsmall); } padding-inline-start: 0; - padding-inline-end: 0; + padding-inline-end: var(--spacing-xxsmall); overflow-y: auto; position: relative; height: $height; box-sizing: border-box; } - -.attribution { - display: flex; - justify-content: flex-end; -} diff --git a/src/components/CommandBar/CommandBarBody/index.tsx b/src/components/CommandBar/CommandBarBody/index.tsx index e9811f75da..8ea5f7547c 100644 --- a/src/components/CommandBar/CommandBarBody/index.tsx +++ b/src/components/CommandBar/CommandBarBody/index.tsx @@ -1,21 +1,19 @@ /* eslint-disable max-lines */ -import React, { useState, useCallback, useEffect } from 'react'; +import React, { useState, useCallback } from 'react'; import classNames from 'classnames'; import groupBy from 'lodash/groupBy'; import useTranslation from 'next-translate/useTranslation'; import { shallowEqual, useSelector } from 'react-redux'; -import CommandsList, { Command } from '../CommandsList'; +import CommandsList, { Command, RESULTS_GROUP } from '../CommandsList'; import styles from './CommandBarBody.module.scss'; import { getNewSearchResults } from '@/api'; import DataFetcher from '@/components/DataFetcher'; -import TarteelAttribution from '@/components/TarteelAttribution/TarteelAttribution'; import VoiceSearchBodyContainer from '@/components/TarteelVoiceSearch/BodyContainer'; import TarteelVoiceSearchTrigger from '@/components/TarteelVoiceSearch/Trigger'; -import useDebounce from '@/hooks/useDebounce'; import IconSearch from '@/icons/search.svg'; import { selectInitialSearchQuery, selectRecentNavigations } from '@/redux/slices/CommandBar/state'; import { selectIsCommandBarVoiceFlowStarted } from '@/redux/slices/voiceSearch'; @@ -63,8 +61,6 @@ const NAVIGATE_TO = [ }, ]; -const DEBOUNCING_PERIOD_MS = 1500; - const CommandBarBody: React.FC = () => { const { t } = useTranslation('common'); const recentNavigations = useSelector( @@ -74,15 +70,6 @@ const CommandBarBody: React.FC = () => { const isVoiceSearchFlowStarted = useSelector(selectIsCommandBarVoiceFlowStarted, shallowEqual); const initialSearchQuery = useSelector(selectInitialSearchQuery, shallowEqual); const [searchQuery, setSearchQuery] = useState(initialSearchQuery || null); - // Debounce search query to avoid having to call the API on every type. The API will be called once the user stops typing. - const debouncedSearchQuery = useDebounce(searchQuery, DEBOUNCING_PERIOD_MS); - - useEffect(() => { - // only when the search query has a value we call the API. - if (debouncedSearchQuery) { - logTextSearchQuery(debouncedSearchQuery, SearchQuerySource.CommandBar); - } - }, [debouncedSearchQuery]); /** * Handle when the search query is changed. @@ -91,7 +78,11 @@ const CommandBarBody: React.FC = () => { * @returns {void} */ const onSearchQueryChange = useCallback((event: React.FormEvent): void => { - setSearchQuery(event.currentTarget.value || null); + const newSearchQuery = event.currentTarget.value; + if (newSearchQuery) { + logTextSearchQuery(newSearchQuery, SearchQuerySource.CommandBar); + } + setSearchQuery(newSearchQuery || null); }, []); /** @@ -145,12 +136,17 @@ const CommandBarBody: React.FC = () => { if (!data) { toBeGroupedCommands = getPreInputCommands(); numberOfCommands = recentNavigations.length + NAVIGATE_TO.length; - } else { + } else if (data.result.navigation.length) { toBeGroupedCommands = [ ...data.result.navigation.map((navigationItem) => ({ ...navigationItem, - group: t('command-bar.navigations'), + group: RESULTS_GROUP, })), + ]; + numberOfCommands = data.result.navigation.length; + } else { + // if there are no results, we will show the search page suggestion as an item + toBeGroupedCommands = [ { key: searchQuery, resultType: SearchNavigationType.SEARCH_PAGE, @@ -158,10 +154,11 @@ const CommandBarBody: React.FC = () => { group: t('search.title'), }, ]; - numberOfCommands = data.result.navigation.length + 1; + numberOfCommands = 1; } return ( ({ ...item, index })), // append the index so that it can be used for keyboard navigation. @@ -220,9 +217,6 @@ const CommandBarBody: React.FC = () => { /> )} -
- -
); }; diff --git a/src/components/CommandBar/CommandBarTrigger/CommandBarTrigger.module.scss b/src/components/CommandBar/CommandBarTrigger/CommandBarTrigger.module.scss index 6200496df3..3c0d54fcc2 100644 --- a/src/components/CommandBar/CommandBarTrigger/CommandBarTrigger.module.scss +++ b/src/components/CommandBar/CommandBarTrigger/CommandBarTrigger.module.scss @@ -8,8 +8,7 @@ $width: calc(11 * var(--spacing-mega)); align-items: center; > svg { - fill: var(--color-text-faded); - opacity: var(--opacity-50); + fill: var(--color-text-default); width: calc(1.4 * var(--spacing-medium)); } @@ -25,25 +24,22 @@ $width: calc(11 * var(--spacing-mega)); top var(--transition-fast) ease; box-sizing: border-box; - background: var(--color-background-elevated); + background-color: var(--color-success-faded); width: 100%; display: flex; align-items: center; padding: 0 calc(1.5 * var(--spacing-medium)); - color: var(--color-text-faded); + color: var(--color-text-default); min-height: calc(3 * var(--spacing-large)); border-radius: var(--border-radius-pill); cursor: pointer; outline: inherit; font-size: var(--font-size-large); inset-block-start: 0; - @include theme.light { - box-shadow: var(--shadow-small); - &:hover { - color: var(--color-text-default); - box-shadow: var(--shadow-large); - } + box-shadow: var(--shadow-strong); + &:hover { + color: var(--color-text-link); } justify-content: space-between; } diff --git a/src/components/CommandBar/CommandsList/CommandControl.tsx b/src/components/CommandBar/CommandsList/CommandControl.tsx index a429726a0c..7d70e6bd39 100644 --- a/src/components/CommandBar/CommandsList/CommandControl.tsx +++ b/src/components/CommandBar/CommandsList/CommandControl.tsx @@ -2,7 +2,6 @@ import React, { MouseEvent } from 'react'; import Button, { ButtonSize, ButtonVariant } from '@/dls/Button/Button'; import CloseIcon from '@/icons/close.svg'; -// import KeyboardInput from '@/dls/KeyboardInput'; interface Props { isClearable: boolean; @@ -29,7 +28,6 @@ const CommandControl: React.FC = ({ ); } if (isSelected) { - // return ; return null; } return null; diff --git a/src/components/CommandBar/CommandsList/CommandList.module.scss b/src/components/CommandBar/CommandsList/CommandList.module.scss index 9cd3696a08..139f945d24 100644 --- a/src/components/CommandBar/CommandsList/CommandList.module.scss +++ b/src/components/CommandBar/CommandsList/CommandList.module.scss @@ -1,6 +1,6 @@ @use "src/styles/breakpoints"; -$itemHeight: calc(1.5 * var(--spacing-mega)); +$itemHeight: calc(1.4 * var(--spacing-mega)); .noResult { text-align: center; font-size: var(--font-size-large); @@ -31,12 +31,12 @@ $itemHeight: calc(1.5 * var(--spacing-mega)); padding-inline-start: var(--spacing-xsmall); padding-inline-end: var(--spacing-xsmall); cursor: pointer; - color: var(--color-text-faded); position: relative; z-index: var(--z-index-default); &.selected { color: var(--color-text-default); - background: var(--color-background-alternative-faint); + background: var(--color-background-alternative-medium); + border-radius: var(--border-radius-rounded); } } diff --git a/src/components/CommandBar/CommandsList/CommandPrefix/CommandPrefix.module.scss b/src/components/CommandBar/CommandsList/CommandPrefix/CommandPrefix.module.scss index f98085d45d..f2ec8462c7 100644 --- a/src/components/CommandBar/CommandsList/CommandPrefix/CommandPrefix.module.scss +++ b/src/components/CommandBar/CommandsList/CommandPrefix/CommandPrefix.module.scss @@ -17,6 +17,9 @@ .name { em { font-weight: var(--font-weight-bold); - text-decoration: underline; + color: var(--color-highlight-dark); + } + sup { + display: none; } } diff --git a/src/components/CommandBar/CommandsList/CommandPrefix/index.tsx b/src/components/CommandBar/CommandsList/CommandPrefix/index.tsx index 6af1d9eda7..e8d8ba52f1 100644 --- a/src/components/CommandBar/CommandsList/CommandPrefix/index.tsx +++ b/src/components/CommandBar/CommandsList/CommandPrefix/index.tsx @@ -5,8 +5,9 @@ import useTranslation from 'next-translate/useTranslation'; import styles from './CommandPrefix.module.scss'; +import SearchResultItemIcon from '@/components/Search/SearchResults/SearchResultItemIcon'; import DataContext from '@/contexts/DataContext'; -import NavigateIcon from '@/icons/east.svg'; +import { Direction } from '@/utils/locale'; import { getSearchNavigationResult } from '@/utils/search'; import { SearchNavigationType } from 'types/Search/SearchNavigationResult'; @@ -20,9 +21,7 @@ interface Props { const CommandPrefix: React.FC = ({ name, type, isVoiceSearch, navigationKey }) => { const { t, lang } = useTranslation('common'); const chapterData = useContext(DataContext); - const getName = () => { - if (isVoiceSearch) return name; - + const getContent = () => { if (type === SearchNavigationType.SEARCH_PAGE) { return t('search-for', { searchQuery: name, @@ -34,7 +33,6 @@ const CommandPrefix: React.FC = ({ name, type, isVoiceSearch, navigationK { resultType: type, key: navigationKey, name }, t, lang, - true, ); return navigation?.name; }; @@ -42,14 +40,17 @@ const CommandPrefix: React.FC = ({ name, type, isVoiceSearch, navigationK return (
- + -

+ {isVoiceSearch ? ( +

{name}
+ ) : ( +

+ )}

); }; diff --git a/src/components/CommandBar/CommandsList/index.tsx b/src/components/CommandBar/CommandsList/index.tsx index 403a469729..5916550a40 100644 --- a/src/components/CommandBar/CommandsList/index.tsx +++ b/src/components/CommandBar/CommandsList/index.tsx @@ -14,15 +14,17 @@ import CommandControl from './CommandControl'; import styles from './CommandList.module.scss'; import CommandPrefix from './CommandPrefix'; +import SearchResultsHeader from '@/components/Search/SearchResults/SearchResultsHeader'; import useScroll, { SMOOTH_SCROLL_TO_CENTER } from '@/hooks/useScrollToElement'; import { addRecentNavigation, removeRecentNavigation, setIsOpen, } from '@/redux/slices/CommandBar/state'; +import SearchQuerySource from '@/types/SearchQuerySource'; import { logButtonClick } from '@/utils/eventLogger'; import { resolveUrlBySearchNavigationType } from '@/utils/navigation'; -import { SearchNavigationResult } from 'types/Search/SearchNavigationResult'; +import { SearchNavigationResult, SearchNavigationType } from 'types/Search/SearchNavigationResult'; export interface Command extends SearchNavigationResult { group: string; @@ -34,9 +36,15 @@ export interface Command extends SearchNavigationResult { interface Props { commandGroups: { groups: Record; numberOfCommands: number }; + searchQuery?: string; } -const CommandsList: React.FC = ({ commandGroups: { groups, numberOfCommands } }) => { +export const RESULTS_GROUP = 'results'; + +const CommandsList: React.FC = ({ + commandGroups: { groups, numberOfCommands }, + searchQuery, +}) => { const { t } = useTranslation('common'); const [scrollToSelectedCommand, selectedItemRef]: [() => void, RefObject] = useScroll(SMOOTH_SCROLL_TO_CENTER); @@ -129,6 +137,15 @@ const CommandsList: React.FC = ({ commandGroups: { groups, numberOfComman dispatch({ type: removeRecentNavigation.type, payload: navigationItemKey }); }; + const onSearchResultsHeaderClicked = () => { + navigateToLink({ + name: searchQuery, + resultType: SearchNavigationType.SEARCH_PAGE, + key: searchQuery, + group: RESULTS_GROUP, + }); + }; + if (numberOfCommands === 0) { return

{t('command-bar.no-nav-results')}

; } @@ -143,9 +160,17 @@ const CommandsList: React.FC = ({ commandGroups: { groups, numberOfComman {Object.keys(groups).map((commandGroup) => { return (
-
- {commandGroup} -
+ {commandGroup === RESULTS_GROUP ? ( + + ) : ( +
+ {commandGroup} +
+ )}
    {groups[commandGroup].map((command) => { const { name, resultType, key, index, isVoiceSearch } = command; diff --git a/src/components/HomePage/HomePageHero.tsx b/src/components/HomePage/HomePageHero.tsx index 5c1b115407..c1d6d54046 100644 --- a/src/components/HomePage/HomePageHero.tsx +++ b/src/components/HomePage/HomePageHero.tsx @@ -15,7 +15,7 @@ const HomePageHero = () => {
    -
    +
    diff --git a/src/components/HomePage/PlayRadioButton.tsx b/src/components/HomePage/PlayRadioButton.tsx index 5d9e2224ab..a2c5f00fc1 100644 --- a/src/components/HomePage/PlayRadioButton.tsx +++ b/src/components/HomePage/PlayRadioButton.tsx @@ -14,6 +14,7 @@ import Button, { ButtonType, ButtonSize } from '@/dls/Button/Button'; import Spinner from '@/dls/Spinner/Spinner'; import PauseIcon from '@/icons/pause.svg'; import PlayIcon from '@/icons/play-arrow.svg'; +import ThemeType from '@/redux/types/ThemeType'; import { logButtonClick } from '@/utils/eventLogger'; import { selectIsLoading } from 'src/xstate/actors/audioPlayer/selectors'; import { AudioPlayerMachineContext } from 'src/xstate/AudioPlayerMachineContext'; @@ -54,7 +55,7 @@ const PlayRadioButton = () => { const { radioActor } = audioService.getSnapshot().context; return ( -
    +
    {isAudioPlaying && isRadioMode ? ( - ); -}; - -export default NavigationItem; diff --git a/src/components/Search/PreInput/SearchQuerySuggestion/index.tsx b/src/components/Search/PreInput/SearchQuerySuggestion/index.tsx index 181f6c6486..52ac5c5bc1 100644 --- a/src/components/Search/PreInput/SearchQuerySuggestion/index.tsx +++ b/src/components/Search/PreInput/SearchQuerySuggestion/index.tsx @@ -1,23 +1,26 @@ import React, { MouseEvent, KeyboardEvent } from 'react'; +import SearchResultItemIcon from '../../SearchResults/SearchResultItemIcon'; import SearchItem from '../SearchItem'; import styles from './SearchQuerySuggestion.module.scss'; import Button, { ButtonShape, ButtonSize, ButtonVariant } from '@/dls/Button/Button'; import CloseIcon from '@/icons/close.svg'; -import SearchIcon from '@/icons/search.svg'; +import { SearchNavigationType } from '@/types/Search/SearchNavigationResult'; interface Props { searchQuery: string; onSearchKeywordClicked: (searchQuery: string) => void; onRemoveSearchQueryClicked?: (searchQuery: string) => void; + type: SearchNavigationType; } const SearchQuerySuggestion: React.FC = ({ searchQuery, onSearchKeywordClicked, onRemoveSearchQueryClicked, + type, }) => { const onRemoveClicked = ( event: MouseEvent | KeyboardEvent, @@ -31,7 +34,7 @@ const SearchQuerySuggestion: React.FC = ({
    } + prefix={} onClick={() => onSearchKeywordClicked(searchQuery)} suffix={ onRemoveSearchQueryClicked && ( diff --git a/src/components/Search/PreInput/index.tsx b/src/components/Search/PreInput/index.tsx index 6cf57d01a5..2b457ea2b4 100644 --- a/src/components/Search/PreInput/index.tsx +++ b/src/components/Search/PreInput/index.tsx @@ -11,6 +11,8 @@ import SearchHistory from '@/components/Search/SearchHistory'; import Link from '@/dls/Link/Link'; import useGetChaptersData from '@/hooks/useGetChaptersData'; import TrendUpIcon from '@/icons/trend-up.svg'; +import { SearchNavigationType } from '@/types/Search/SearchNavigationResult'; +import SearchQuerySource from '@/types/SearchQuerySource'; import { getChapterData } from '@/utils/chapter'; import { logButtonClick } from '@/utils/eventLogger'; import { toLocalizedNumber, toLocalizedVerseKey } from '@/utils/locale'; @@ -18,23 +20,39 @@ import { getSurahNavigationUrl } from '@/utils/navigation'; interface Props { onSearchKeywordClicked: (searchQuery: string) => void; - isSearchDrawer: boolean; + source: SearchQuerySource; } const POPULAR_SEARCH_QUERIES = { Mulk: 67, Noah: 71, Kahf: 18, Yaseen: 36 }; -const PreInput: React.FC = ({ onSearchKeywordClicked, isSearchDrawer }) => { +const PreInput: React.FC = ({ onSearchKeywordClicked, source }) => { const { t, lang } = useTranslation('common'); const chaptersData = useGetChaptersData(lang); if (!chaptersData) { return <>; } - const SEARCH_FOR_KEYWORDS = [ - `${t('juz')} ${toLocalizedNumber(1, lang)}`, - `${t('page')} ${toLocalizedNumber(1, lang)}`, - getChapterData(chaptersData, '36').transliteratedName, - toLocalizedNumber(36, lang), - toLocalizedVerseKey('2:255', lang), + + const SEARCH_FOR_KEYWORD = [ + { + type: SearchNavigationType.JUZ, + value: `${t('juz')} ${toLocalizedNumber(1, lang)}`, + }, + { + type: SearchNavigationType.PAGE, + value: `${t('page')} ${toLocalizedNumber(1, lang)}`, + }, + { + type: SearchNavigationType.SURAH, + value: getChapterData(chaptersData, '36').transliteratedName, + }, + { + type: SearchNavigationType.SURAH, + value: toLocalizedNumber(36, lang), + }, + { + type: SearchNavigationType.AYAH, + value: toLocalizedVerseKey('2:255', lang), + }, ]; return (
    @@ -52,29 +70,23 @@ const PreInput: React.FC = ({ onSearchKeywordClicked, isSearchDrawer }) = title={chapterData.transliteratedName} key={url} onClick={() => { - logButtonClick( - `search_${ - isSearchDrawer ? 'drawer' : 'page' - }_popular_search_${popularSearchQuery}`, - ); + logButtonClick(`${source}_popular_search_${popularSearchQuery}`); }} /> ); })}
    - +
    - {SEARCH_FOR_KEYWORDS.map((keyword, index) => { + {SEARCH_FOR_KEYWORD.map((keyword, index) => { return ( { - logButtonClick(`search_${isSearchDrawer ? 'drawer' : 'page'}_search_hint_${index}`); + logButtonClick(`${source}_search_hint_${index}`); onSearchKeywordClicked(searchQuery); }} /> diff --git a/src/components/Search/SearchBodyContainer.tsx b/src/components/Search/SearchBodyContainer.tsx index 3d6047b254..9625687f63 100644 --- a/src/components/Search/SearchBodyContainer.tsx +++ b/src/components/Search/SearchBodyContainer.tsx @@ -9,12 +9,12 @@ import styles from './SearchBodyContainer.module.scss'; import SearchResults from '@/components/Search/SearchResults'; import Spinner, { SpinnerSize } from '@/dls/Spinner/Spinner'; +import SearchQuerySource from '@/types/SearchQuerySource'; import { SearchResponse } from 'types/ApiResponses'; interface Props { searchQuery: string; isSearching: boolean; - isSearchDrawer?: boolean; hasError: boolean; searchResult: SearchResponse; onSearchKeywordClicked: (keyword: string) => void; @@ -23,6 +23,7 @@ interface Props { pageSize?: number; onPageChange?: (page: number) => void; shouldSuggestFullSearchWhenNoResults?: boolean; + source: SearchQuerySource; } const SearchBodyContainer: React.FC = ({ @@ -32,11 +33,11 @@ const SearchBodyContainer: React.FC = ({ searchResult, onSearchKeywordClicked, onSearchResultClicked, - isSearchDrawer = true, currentPage, pageSize, onPageChange, shouldSuggestFullSearchWhenNoResults = false, + source, }) => { const { t } = useTranslation('common'); const isEmptyResponse = @@ -52,7 +53,7 @@ const SearchBodyContainer: React.FC = ({ })} > {!searchQuery ? ( - + ) : ( <> {isSearching ? ( @@ -72,7 +73,7 @@ const SearchBodyContainer: React.FC = ({ onSearchResultClicked={onSearchResultClicked} searchResult={searchResult} searchQuery={searchQuery} - isSearchDrawer={isSearchDrawer} + source={source} currentPage={currentPage} onPageChange={onPageChange} pageSize={pageSize} diff --git a/src/components/Search/SearchHistory/index.tsx b/src/components/Search/SearchHistory/index.tsx index eb0c208764..cd9fc233c5 100644 --- a/src/components/Search/SearchHistory/index.tsx +++ b/src/components/Search/SearchHistory/index.tsx @@ -8,15 +8,17 @@ import styles from './SearchHistory.module.scss'; import Header from '@/components/Search/PreInput/Header'; import SearchQuerySuggestion from '@/components/Search/PreInput/SearchQuerySuggestion'; import { removeSearchHistoryRecord, selectSearchHistory } from '@/redux/slices/Search/search'; +import { SearchNavigationType } from '@/types/Search/SearchNavigationResult'; +import SearchQuerySource from '@/types/SearchQuerySource'; import { areArraysEqual } from '@/utils/array'; import { logButtonClick } from '@/utils/eventLogger'; interface Props { onSearchKeywordClicked: (searchQuery: string) => void; - isSearchDrawer: boolean; + source: SearchQuerySource; } -const SearchHistory: React.FC = ({ onSearchKeywordClicked, isSearchDrawer }) => { +const SearchHistory: React.FC = ({ onSearchKeywordClicked, source }) => { const { t } = useTranslation('common'); const searchHistory = useSelector(selectSearchHistory, areArraysEqual) as string[]; const dispatch = useDispatch(); @@ -24,10 +26,10 @@ const SearchHistory: React.FC = ({ onSearchKeywordClicked, isSearchDrawer const onRemoveSearchQueryClicked = useCallback( (searchQuery: string) => { // eslint-disable-next-line i18next/no-literal-string - logButtonClick(`search_${isSearchDrawer ? 'drawer' : 'page'}_remove_query`); + logButtonClick(`${source}_remove_query`); dispatch({ type: removeSearchHistoryRecord.type, payload: searchQuery }); }, - [dispatch, isSearchDrawer], + [dispatch, source], ); // if there are no recent search queries. @@ -39,10 +41,11 @@ const SearchHistory: React.FC = ({ onSearchKeywordClicked, isSearchDrawer
    {searchHistory.map((recentSearchQuery) => ( { - logButtonClick(`search_${isSearchDrawer ? 'drawer' : 'page'}_history_item`); + logButtonClick(`${source}_history_item`); onSearchKeywordClicked(searchQuery); }} onRemoveSearchQueryClicked={onRemoveSearchQueryClicked} diff --git a/src/components/Search/SearchInput/SearchInput.module.scss b/src/components/Search/SearchInput/SearchInput.module.scss new file mode 100644 index 0000000000..0d1146f4d0 --- /dev/null +++ b/src/components/Search/SearchInput/SearchInput.module.scss @@ -0,0 +1,25 @@ +@use "src/styles/breakpoints"; + +$tablet-max-width: 50rem; + +.headerOuterContainer { + margin-block-end: var(--spacing-medium); +} + +.headerInnerContainer { + padding-inline: calc(1.5 * var(--spacing-medium)); + @include breakpoints.tablet { + padding-inline: calc(2 * var(--spacing-mega)); + } + max-width: $tablet-max-width; + margin-inline: auto; +} + +.inputContainer { + border-radius: var(--border-radius-pill) !important; + box-shadow: var(--shadow-strong); +} + +.prefixSuffixContainer { + color: var(--color-text-default) !important; +} diff --git a/src/components/Search/SearchInput/index.tsx b/src/components/Search/SearchInput/index.tsx new file mode 100644 index 0000000000..f2b756a175 --- /dev/null +++ b/src/components/Search/SearchInput/index.tsx @@ -0,0 +1,60 @@ +import React, { RefObject } from 'react'; + +import useTranslation from 'next-translate/useTranslation'; +import { useDispatch } from 'react-redux'; + +import styles from './SearchInput.module.scss'; + +import Input from '@/dls/Forms/Input'; +import SearchIcon from '@/icons/search.svg'; +import { setInitialSearchQuery, setIsOpen } from '@/redux/slices/CommandBar/state'; +import { logButtonClick } from '@/utils/eventLogger'; + +type Props = { + inputRef?: RefObject; + searchQuery?: string; + isSearching?: boolean; + setSearchQuery: (query: string) => void; +}; + +const SearchInput: React.FC = ({ inputRef, searchQuery, isSearching, setSearchQuery }) => { + const { t } = useTranslation('common'); + const dispatch = useDispatch(); + /** + * Handle when the search query is changed. + * + * @param {string} newSearchQuery + * @returns {void} + */ + const onSearchQueryChange = (newSearchQuery: string): void => { + dispatch({ type: setIsOpen.type, payload: true }); + dispatch({ type: setInitialSearchQuery.type, payload: newSearchQuery }); + }; + + const onClearClicked = () => { + logButtonClick('search_page_clear_query'); + setSearchQuery(''); + }; + return ( +
    +
    + } + onChange={onSearchQueryChange} + onClearClicked={onClearClicked} + inputRef={inputRef} + clearable + value={searchQuery} + disabled={isSearching} + placeholder={t('search.title')} + fixedWidth={false} + containerClassName={styles.inputContainer} + prefixSuffixContainerClassName={styles.prefixSuffixContainer} + /> +
    +
    + ); +}; + +export default SearchInput; diff --git a/src/components/Search/SearchResults/SearchResultItem.module.scss b/src/components/Search/SearchResults/SearchResultItem.module.scss deleted file mode 100644 index 1d4071a515..0000000000 --- a/src/components/Search/SearchResults/SearchResultItem.module.scss +++ /dev/null @@ -1,33 +0,0 @@ -.container { - text-decoration: none; - border-block-end: 1px solid var(--color-borders-hairline); - padding-block: var(--spacing-medium); - margin-block-end: var(--spacing-xsmall); -} - -.itemContainer { - border-radius: var(--border-radius-default); - margin-inline-start: auto; - margin-inline-end: auto; -} - -.quranTextResult { - font-size: var(--font-size-xlarge); - line-height: var(--line-height-large); - padding-block-start: var(--spacing-micro); - padding-block-end: var(--spacing-micro); - padding-inline-start: var(--spacing-micro); - padding-inline-end: var(--spacing-micro); - - em { - font-weight: var(--font-weight-semibold); - text-decoration: underline; - } -} - -.verseKey { - color: var(--color-success-medium); - display: block; - margin-block-end: var(--spacing-medium); - font-weight: var(--font-weight-bold); -} diff --git a/src/components/Search/SearchResults/SearchResultItem.tsx b/src/components/Search/SearchResults/SearchResultItem.tsx deleted file mode 100644 index db51c3190e..0000000000 --- a/src/components/Search/SearchResults/SearchResultItem.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; - -import useTranslation from 'next-translate/useTranslation'; - -import KalimatNavigationSearchResultItem from './KalimatNavigationSearchResultItem'; -import styles from './SearchResultItem.module.scss'; - -import Button from '@/dls/Button/Button'; -import { SearchNavigationType } from '@/types/Search/SearchNavigationResult'; -import SearchService from '@/types/Search/SearchService'; -import SearchVerseItem from '@/types/Search/SearchVerseItem'; -import SearchQuerySource from '@/types/SearchQuerySource'; -import { logButtonClick } from '@/utils/eventLogger'; -import { toLocalizedNumber, toLocalizedVerseKey } from '@/utils/locale'; -import { resolveUrlBySearchNavigationType } from '@/utils/navigation'; - -interface Props { - result: SearchVerseItem; - source: SearchQuerySource; - service?: SearchService; -} - -const SearchResultItem: React.FC = ({ result, source, service = SearchService.KALIMAT }) => { - const { lang } = useTranslation('quran-reader'); - const url = resolveUrlBySearchNavigationType(result.resultType, result.key, true); - - const getKalimatResultSuffix = () => { - if (result.resultType === SearchNavigationType.SURAH) { - return `(${toLocalizedNumber(Number(result.key), lang)})`; - } - - if (result.resultType === SearchNavigationType.AYAH) { - return `(${toLocalizedVerseKey(result.key as string, lang)})`; - } - - return undefined; - }; - - const suffix = getKalimatResultSuffix(); - - const onResultItemClicked = () => { - logButtonClick(`search_result_item`, { - service, - source, - }); - }; - - return ( -
    -
    -
    - {result.resultType === SearchNavigationType.AYAH ? ( - - ) : ( - - )} -
    -
    -
    - ); -}; -export default SearchResultItem; diff --git a/src/components/Search/SearchResults/SearchResultItem/SearchResultItem.module.scss b/src/components/Search/SearchResults/SearchResultItem/SearchResultItem.module.scss new file mode 100644 index 0000000000..8263b6df1c --- /dev/null +++ b/src/components/Search/SearchResults/SearchResultItem/SearchResultItem.module.scss @@ -0,0 +1,18 @@ +.iconContainer { + padding-block-start: var(--spacing-micro); + padding-inline-end: var(--spacing-small); +} + +.container { + padding-block: var(--spacing-xsmall); + padding-inline: var(--spacing-small); + &:hover { + background-color: var(--color-background-alternative-faded); + border-radius: var(--border-radius-default); + } +} + +.linkContainer { + display: flex; + flex-direction: row; +} diff --git a/src/components/Search/SearchResults/KalimatNavigationSearchResultItem.tsx b/src/components/Search/SearchResults/SearchResultItem/index.tsx similarity index 53% rename from src/components/Search/SearchResults/KalimatNavigationSearchResultItem.tsx rename to src/components/Search/SearchResults/SearchResultItem/index.tsx index ffaf9e3325..9b0c4911e6 100644 --- a/src/components/Search/SearchResults/KalimatNavigationSearchResultItem.tsx +++ b/src/components/Search/SearchResults/SearchResultItem/index.tsx @@ -1,9 +1,10 @@ /* eslint-disable react/no-danger */ - import React, { useContext } from 'react'; import useTranslation from 'next-translate/useTranslation'; +import SearchResultItemIcon from '../SearchResultItemIcon'; + import styles from './SearchResultItem.module.scss'; import DataContext from '@/contexts/DataContext'; @@ -11,42 +12,45 @@ import Link from '@/dls/Link/Link'; import { SearchNavigationType } from '@/types/Search/SearchNavigationResult'; import SearchService from '@/types/Search/SearchService'; import SearchQuerySource from '@/types/SearchQuerySource'; -import { getChapterData } from '@/utils/chapter'; import { logButtonClick } from '@/utils/eventLogger'; -import { toLocalizedVerseKey } from '@/utils/locale'; import { resolveUrlBySearchNavigationType } from '@/utils/navigation'; +import { getResultSuffix } from '@/utils/search'; import { getVerseAndChapterNumbersFromKey } from '@/utils/verse'; interface Props { name: string; resultKey: string | number; source: SearchQuerySource; + service: SearchService; + type: SearchNavigationType; } -const KalimatNavigationSearchResultItem: React.FC = ({ name, source, resultKey }) => { - const { t, lang } = useTranslation(); +const AyahSearchResultItem: React.FC = ({ name, source, resultKey, type, service }) => { + const { lang } = useTranslation(); const chaptersData = useContext(DataContext); const [surahNumber] = getVerseAndChapterNumbersFromKey(resultKey as string); const onResultItemClicked = () => { logButtonClick(`search_result_item`, { - service: SearchService.KALIMAT, + service, source, }); }; - const url = resolveUrlBySearchNavigationType(SearchNavigationType.AYAH, resultKey, true); - + const suffix = getResultSuffix(type, resultKey as string, lang, chaptersData, surahNumber); + const url = resolveUrlBySearchNavigationType(type, resultKey, true); return ( - <> - - {`${t('common:surah')} ${ - getChapterData(chaptersData, `${surahNumber}`).transliteratedName - } (${toLocalizedVerseKey(resultKey as string, lang)})`} +
    + +
    + +
    +
    -
    -
    -
    - +
    ); }; -export default KalimatNavigationSearchResultItem; +export default AyahSearchResultItem; diff --git a/src/components/Search/SearchResults/SearchResultItemIcon/index.tsx b/src/components/Search/SearchResults/SearchResultItemIcon/index.tsx new file mode 100644 index 0000000000..f4cd903293 --- /dev/null +++ b/src/components/Search/SearchResults/SearchResultItemIcon/index.tsx @@ -0,0 +1,35 @@ +import NavigateToIcon from '@/icons/east.svg'; +import ArabicIcon from '@/icons/search/arabic.svg'; +import AyahRangeIcon from '@/icons/search/ayah-range.svg'; +import JuzIcon from '@/icons/search/juz.svg'; +import PageIcon from '@/icons/search/page.svg'; +import SurahIcon from '@/icons/search/surah.svg'; +import TranslationIcon from '@/icons/search/translation.svg'; +import SearchIcon from '@/icons/search.svg'; +import { SearchNavigationType } from '@/types/Search/SearchNavigationResult'; + +const TYPE_ICON_MAP = { + [SearchNavigationType.AYAH]: TranslationIcon, + [SearchNavigationType.SURAH]: SurahIcon, + [SearchNavigationType.JUZ]: JuzIcon, + [SearchNavigationType.PAGE]: PageIcon, + [SearchNavigationType.RANGE]: AyahRangeIcon, + [SearchNavigationType.RUB_EL_HIZB]: ArabicIcon, + [SearchNavigationType.HIZB]: ArabicIcon, + [SearchNavigationType.SEARCH_PAGE]: SearchIcon, +}; + +interface Props { + type: SearchNavigationType; +} + +const SearchResultItemIcon = ({ type }: Props) => { + const Icon = TYPE_ICON_MAP[type]; + if (!type) { + return <>; + } + + return Icon ? : ; +}; + +export default SearchResultItemIcon; diff --git a/src/components/Search/SearchResults/SearchResults.module.scss b/src/components/Search/SearchResults/SearchResults.module.scss deleted file mode 100644 index 1aec0724e7..0000000000 --- a/src/components/Search/SearchResults/SearchResults.module.scss +++ /dev/null @@ -1,28 +0,0 @@ -.resultsSummaryContainer { - margin-block-start: var(--spacing-medium); - display: flex; - align-items: center; - justify-content: space-between; - padding-block-end: var(--spacing-medium); -} - -.header { - margin-block-start: var(--spacing-large); - text-transform: capitalize; - color: var(--color-text-faded); -} - -.showAll { - text-decoration: underline; -} - -.navigationItemsListContainer { - margin-block-start: var(--spacing-large); -} -.navigationItemContainer { - margin-inline-end: var(--spacing-small); - em { - font-weight: var(--font-weight-semibold); - text-decoration: underline; - } -} diff --git a/src/components/Search/SearchResults/SearchResultsHeader/SearchResultsHeader.module.scss b/src/components/Search/SearchResults/SearchResultsHeader/SearchResultsHeader.module.scss new file mode 100644 index 0000000000..adafaa6160 --- /dev/null +++ b/src/components/Search/SearchResults/SearchResultsHeader/SearchResultsHeader.module.scss @@ -0,0 +1,25 @@ +.showAll { + font-weight: var(--font-weight-bold); +} + +.commandPrefix { + margin-inline-start: var(--spacing-xxsmall); + display: flex; + align-items: center; + > svg { + width: var(--spacing-large); + height: var(--spacing-large); + } +} + +.moreResultsContainer { + display: flex; + align-items: center; +} + +.resultsSummaryContainer { + display: flex; + align-items: center; + justify-content: space-between; + color: var(--color-text-faded); +} diff --git a/src/components/Search/SearchResults/SearchResultsHeader/index.tsx b/src/components/Search/SearchResults/SearchResultsHeader/index.tsx new file mode 100644 index 0000000000..980c9ed13a --- /dev/null +++ b/src/components/Search/SearchResults/SearchResultsHeader/index.tsx @@ -0,0 +1,48 @@ +import React from 'react'; + +import useTranslation from 'next-translate/useTranslation'; + +import styles from './SearchResultsHeader.module.scss'; + +import IconContainer from '@/dls/IconContainer/IconContainer'; +import Link from '@/dls/Link/Link'; +import NavigateIcon from '@/icons/east.svg'; +import SearchQuerySource from '@/types/SearchQuerySource'; +import { logButtonClick } from '@/utils/eventLogger'; +import { getSearchQueryNavigationUrl } from '@/utils/navigation'; + +type Props = { + searchQuery: string; + onSearchResultClicked?: () => void; + source: SearchQuerySource; +}; + +const SearchResultsHeader: React.FC = ({ searchQuery, onSearchResultClicked, source }) => { + const { t } = useTranslation(); + + const onNavigationLinkClicked = () => { + if (onSearchResultClicked) { + onSearchResultClicked(); + } + logButtonClick(`${source}_show_all`); + }; + return ( +
    +

    {t('common:search-results-no-count')}

    + + +

    {t('common:search.more-results')}

    + + } /> + +
    + +
    + ); +}; + +export default SearchResultsHeader; diff --git a/src/components/Search/SearchResults/index.tsx b/src/components/Search/SearchResults/index.tsx index 3016fd8c6f..c6b71c91b0 100644 --- a/src/components/Search/SearchResults/index.tsx +++ b/src/components/Search/SearchResults/index.tsx @@ -3,104 +3,73 @@ import React from 'react'; import useTranslation from 'next-translate/useTranslation'; import SearchResultItem from './SearchResultItem'; -import styles from './SearchResults.module.scss'; +import SearchResultsHeader from './SearchResultsHeader'; -import NavigationItem from '@/components/Search/NavigationItem'; -import Link from '@/dls/Link/Link'; import Pagination from '@/dls/Pagination/Pagination'; import SearchQuerySource from '@/types/SearchQuerySource'; -import { logButtonClick } from '@/utils/eventLogger'; import { toLocalizedNumber } from '@/utils/locale'; import { SearchResponse } from 'types/ApiResponses'; interface Props { searchResult: SearchResponse; searchQuery: string; - isSearchDrawer?: boolean; currentPage?: number; pageSize?: number; onPageChange?: (page: number) => void; onSearchResultClicked?: () => void; + source: SearchQuerySource; } const SearchResults: React.FC = ({ searchResult, searchQuery, - isSearchDrawer = true, + source, currentPage, onPageChange, pageSize, onSearchResultClicked, }) => { - const { t, lang } = useTranslation(); + const results = searchResult.result.navigation.concat(searchResult.result.verses); + const isSearchDrawer = source === SearchQuerySource.SearchDrawer; + const { t, lang } = useTranslation('common'); return ( - <> -
    - {!!searchResult.result.navigation?.length && ( -
    - {searchResult.result.navigation.map((navigationResult) => ( - - - - ))} -
    - )} -

    - {t('common:search-results', { - count: toLocalizedNumber(searchResult.pagination.totalRecords, lang), - })} -

    +
    + {isSearchDrawer ? ( + + ) : ( <> - {searchResult.result.verses.map((result) => ( - - ))} - {isSearchDrawer ? ( -
    -

    - {toLocalizedNumber(searchResult.pagination.totalRecords, lang)}{' '} - {t('common:search.results')} -

    - {searchResult.pagination.totalRecords > 0 && ( - { - if (onSearchResultClicked) onSearchResultClicked(); - logButtonClick('search_drawer_show_all'); - }} - > - -

    {t('common:search.show-all')}

    -
    - - )} -
    - ) : ( + {searchQuery && ( <> - {searchQuery && ( - - )} + {t('search-results', { + count: toLocalizedNumber(searchResult.pagination.totalRecords, lang), + })} + )} -
    - + )} + <> + {results.map((result) => ( + + ))} + +
    ); }; diff --git a/src/components/TarteelVoiceSearch/BodyContainer/SearchResults.tsx b/src/components/TarteelVoiceSearch/BodyContainer/SearchResults.tsx index 94abdd993d..b4d4a59c40 100644 --- a/src/components/TarteelVoiceSearch/BodyContainer/SearchResults.tsx +++ b/src/components/TarteelVoiceSearch/BodyContainer/SearchResults.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useContext } from 'react'; import groupBy from 'lodash/groupBy'; import useTranslation from 'next-translate/useTranslation'; @@ -7,13 +7,13 @@ import { useSelector } from 'react-redux'; import CommandsList from '@/components/CommandBar/CommandsList'; import DataFetcher from '@/components/DataFetcher'; import TarteelSearchResultItem from '@/components/TarteelVoiceSearch/TarteelSearchResultItem'; +import DataContext from '@/contexts/DataContext'; import { selectSelectedTranslations } from '@/redux/slices/QuranReader/translations'; import SearchService from '@/types/Search/SearchService'; import SearchQuerySource from '@/types/SearchQuerySource'; import { makeVersesFilterUrl } from '@/utils/apiPaths'; import { areArraysEqual } from '@/utils/array'; -import { toLocalizedVerseKey } from '@/utils/locale'; -import { truncateString } from '@/utils/string'; +import { getResultSuffix } from '@/utils/search'; import { VersesResponse } from 'types/ApiResponses'; import { SearchNavigationType } from 'types/Search/SearchNavigationResult'; import SearchResult from 'types/Tarteel/SearchResult'; @@ -26,6 +26,7 @@ interface Props { const SearchResults: React.FC = ({ searchResult, isCommandBar }) => { const selectedTranslations = useSelector(selectSelectedTranslations, areArraysEqual); const { t, lang } = useTranslation('common'); + const chaptersData = useContext(DataContext); const params = { // only get the first 10 results @@ -33,7 +34,7 @@ const SearchResults: React.FC = ({ searchResult, isCommandBar }) => { .slice(0, 10) .map((match) => `${match.surahNum}:${match.ayahNum}`) .join(','), - fields: 'text_uthmani', + fields: 'text_uthmani,chapter_id', // when it's the search drawer ...(!isCommandBar && { words: true, @@ -52,9 +53,12 @@ const SearchResults: React.FC = ({ searchResult, isCommandBar }) => { return { key: verse.verseKey, resultType: SearchNavigationType.AYAH, - name: `[${toLocalizedVerseKey(verse.verseKey, lang)}] ${truncateString( - verse.textUthmani, - 80, + name: `${verse.textUthmani} ${getResultSuffix( + SearchNavigationType.AYAH, + verse.verseKey, + lang, + chaptersData, + verse.chapterId.toString(), )}`, isVoiceSearch: true, group: t('command-bar.navigations'), @@ -86,7 +90,7 @@ const SearchResults: React.FC = ({ searchResult, isCommandBar }) => { ); }, - [isCommandBar, lang, t], + [chaptersData, isCommandBar, lang, t], ); return ; diff --git a/src/components/dls/Forms/Input/index.tsx b/src/components/dls/Forms/Input/index.tsx index e6be0b4e3c..49dadfcddc 100644 --- a/src/components/dls/Forms/Input/index.tsx +++ b/src/components/dls/Forms/Input/index.tsx @@ -56,6 +56,7 @@ interface Props { htmlType?: React.HTMLInputTypeAttribute; isRequired?: boolean; inputRef?: RefObject; + prefixSuffixContainerClassName?: string; } const Input: React.FC = ({ @@ -78,6 +79,7 @@ const Input: React.FC = ({ value = '', shouldFlipOnRTL = true, containerClassName, + prefixSuffixContainerClassName, htmlType, isRequired, inputRef, @@ -113,7 +115,15 @@ const Input: React.FC = ({ })} > {prefix && ( -
    {prefix}
    +
    + {prefix} +
    )} = (): JSX.Element => { const { t, lang } = useTranslation('common'); const router = useRouter(); const [searchQuery, setSearchQuery] = useState(''); const [focusInput, searchInputRef]: [() => void, RefObject] = useFocus(); const [currentPage, setCurrentPage] = useState(1); - const [selectedLanguages, setSelectedLanguages] = useState(''); const [isSearching, setIsSearching] = useState(false); const [hasError, setHasError] = useState(false); const [searchResult, setSearchResult] = useState(null); @@ -52,57 +52,33 @@ const Search: NextPage = (): JSX.Element => { // the query params that we want added to the url const queryParams = useMemo( () => ({ - page: currentPage, - languages: selectedLanguages, - q: debouncedSearchQuery, + [QueryParam.PAGE]: currentPage, + [QueryParam.QUERY_OLD]: debouncedSearchQuery, }), - [currentPage, debouncedSearchQuery, selectedLanguages], + [currentPage, debouncedSearchQuery], ); - useAddQueryParamsToUrl('/search', queryParams); + useAddQueryParamsToUrl(navigationUrl, queryParams); // We need this since pages that are statically optimized will be hydrated // without their route parameters provided, i.e query will be an empty object ({}). // After hydration, Next.js will trigger an update to provide the route parameters // in the query object. @see https://nextjs.org/docs/routing/dynamic-routes#caveats useEffect(() => { - // we don't want to focus the main search input when the translation filter modal is open. if (router.isReady) { focusInput(); } }, [focusInput, router]); useEffect(() => { - if (router.query.q || router.query.query) { - let query = router.query.q as string; - if (router.query.query) { - query = router.query.query as string; - } - setSearchQuery(query); + const query = router.query[QueryParam.QUERY] || router.query[QueryParam.QUERY_OLD]; + if (query) { + setSearchQuery(query as string); } - if (router.query.page) { - setCurrentPage(Number(router.query.page)); + if (router.query[QueryParam.PAGE]) { + setCurrentPage(Number(router.query[QueryParam.PAGE])); } - if (router.query.languages) { - setSelectedLanguages(router.query.languages as string); - } - }, [router.query.q, router.query.query, router.query.page, router.query.languages]); - - /** - * Handle when the search query is changed. - * - * @param {string} newSearchQuery - * @returns {void} - */ - const onSearchQueryChange = (newSearchQuery: string): void => { - dispatch({ type: setIsOpen.type, payload: true }); - dispatch({ type: setInitialSearchQuery.type, payload: newSearchQuery }); - }; - - const onClearClicked = () => { - logButtonClick('search_page_clear_query'); - setSearchQuery(''); - }; + }, [router.query]); /** * Call BE to fetch the results using the passed filters. @@ -111,17 +87,8 @@ const Search: NextPage = (): JSX.Element => { * @param {number} page * @param {string} language */ - const getResults = useCallback((query: string, page: number, language: string) => { - searchGetResults( - SearchQuerySource.SearchPage, - query, - page, - PAGE_SIZE, - setIsSearching, - setHasError, - setSearchResult, - language, - ); + const getResults = useCallback((query: string, page: number) => { + searchGetResults(source, query, page, PAGE_SIZE, setIsSearching, setHasError, setSearchResult); }, []); // a ref to know whether this is the initial search request made when the user loads the page or not @@ -136,13 +103,12 @@ const Search: NextPage = (): JSX.Element => { setCurrentPage(1); } - addToSearchHistory(dispatch, debouncedSearchQuery, SearchQuerySource.SearchPage); + addToSearchHistory(dispatch, debouncedSearchQuery, source); getResults( debouncedSearchQuery, // if it is the initial search request, use the page number in the url, otherwise, reset it isInitialSearch.current ? currentPage : 1, - selectedLanguages, ); // if it was the initial request, update the ref @@ -151,22 +117,16 @@ const Search: NextPage = (): JSX.Element => { } } // we don't want to run this effect when currentPage is changed - // because we are already handeling this in onPageChange + // because we are already handling this in onPageChange // eslint-disable-next-line react-hooks/exhaustive-deps - }, [debouncedSearchQuery, getResults, selectedLanguages]); + }, [debouncedSearchQuery, getResults]); const onPageChange = (page: number) => { logEvent('search_page_number_change', { page }); setCurrentPage(page); - getResults(debouncedSearchQuery, page, selectedLanguages); + getResults(debouncedSearchQuery, page); }; - const onSearchKeywordClicked = useCallback((keyword: string) => { - setSearchQuery(keyword); - }, []); - - const navigationUrl = '/search'; - return ( <> = (): JSX.Element => { languageAlternates={getLanguageAlternates(navigationUrl)} />
    -
    -
    - } - onChange={onSearchQueryChange} - onClearClicked={onClearClicked} - inputRef={searchInputRef} - clearable - value={searchQuery} - disabled={isSearching} - placeholder={t('search.title')} - fixedWidth={false} - variant={InputVariant.Main} - /> -
    -
    +
    { const { key, resultType } = result; @@ -139,6 +138,22 @@ export const getSearchNavigationResult = ( }; } + if (resultType === SearchNavigationType.RUB_EL_HIZB) { + return { + name: `${t('common:rub')} ${toLocalizedNumber(Number(key), locale)}`, + key, + resultType: SearchNavigationType.RUB_EL_HIZB, + }; + } + + if (resultType === SearchNavigationType.HIZB) { + return { + name: `${t('common:hizb')} ${toLocalizedNumber(Number(key), locale)}`, + key, + resultType: SearchNavigationType.HIZB, + }; + } + if (resultType === SearchNavigationType.RANGE) { const { surah, from, to } = getVerseNumberRangeFromKey(key as string); return { @@ -151,18 +166,8 @@ export const getSearchNavigationResult = ( } if (resultType === SearchNavigationType.AYAH) { - if (shouldUseOriginalAyahName) { - return { - name: `${result.name} - (${key})`, - key, - resultType: SearchNavigationType.AYAH, - }; - } - const [surahNumber, ayahNumber] = getVerseAndChapterNumbersFromKey(key as string); return { - name: `${t('common:surah')} ${ - getChapterData(chaptersData, `${surahNumber}`).transliteratedName - }, ${t('common:ayah')} ${toLocalizedNumber(Number(ayahNumber), locale)}`, + name: result.name, key, resultType: SearchNavigationType.AYAH, }; @@ -186,7 +191,6 @@ export const getSearchNavigationResult = ( * @param {(arg: boolean) => void} setIsSearching * @param {(arg: boolean) => void} setHasError * @param {(data: SearchResponse) => void} setSearchResult - * @param {string} languages */ export const searchGetResults = ( source: SearchQuerySource, @@ -196,7 +200,6 @@ export const searchGetResults = ( setIsSearching: (arg: boolean) => void, setHasError: (arg: boolean) => void, setSearchResult: (data: SearchResponse) => void, - languages?: string, ) => { setIsSearching(true); logTextSearchQuery(query, source); @@ -204,7 +207,6 @@ export const searchGetResults = ( mode: SearchMode.Advanced, query, size: pageSize, - filterLanguages: languages, page, exactMatchesOnly: 0, getText: 1, @@ -268,3 +270,23 @@ export const getQuickSearchQuery = (query: string): SearchRequestParams { + if (type === SearchNavigationType.SURAH) { + return `- ${toLocalizedNumber(Number(resultKey), lang)}`; + } + + if (type === SearchNavigationType.AYAH) { + return `(${ + getChapterData(chaptersData, `${surahNumber}`).transliteratedName + } ${toLocalizedVerseKey(resultKey as string, lang)})`; + } + + return ''; +}; diff --git a/types/QueryParam.ts b/types/QueryParam.ts index b3b2f5a562..f93fbe49f1 100644 --- a/types/QueryParam.ts +++ b/types/QueryParam.ts @@ -7,6 +7,7 @@ enum QueryParam { FLOW = 'flow', STARTING_VERSE = 'startingVerse', QUERY = 'query', + QUERY_OLD = 'q', REDIRECT_TO = 'r', VERSE_TO = 'verseTo', VERSE_FROM = 'verseFrom', @@ -23,6 +24,7 @@ enum QueryParam { ORIENTATION = 'orientation', VIDEO_ID = 'videoId', SURAH = 'surah', + PAGE = 'page', } export default QueryParam; diff --git a/types/Search/SearchNavigationResult.ts b/types/Search/SearchNavigationResult.ts index bd3faa3100..31e877314c 100644 --- a/types/Search/SearchNavigationResult.ts +++ b/types/Search/SearchNavigationResult.ts @@ -7,6 +7,7 @@ export enum SearchNavigationType { SEARCH_PAGE = 'search_page', PAGE = 'page', RANGE = 'range', + HISTORY = 'history', } export interface SearchNavigationResult {