From 9d3ff84d0e81e983cc08cdf40189bb23988f6512 Mon Sep 17 00:00:00 2001 From: jabahum Date: Mon, 16 Sep 2024 15:24:02 +0300 Subject: [PATCH 1/3] set up switcher --- .../app-search-bar.component.tsx | 102 ++++++++++++++++++ .../app-search-bar/app-search-bar.scss | 13 +++ .../app-search-icon.component.tsx | 78 ++++++++++++++ .../app-search-icon/app-search-icon.scss | 15 +++ .../app-search-overlay.component.tsx | 30 ++++++ 5 files changed, 238 insertions(+) create mode 100644 packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-bar/app-search-bar.component.tsx create mode 100644 packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-bar/app-search-bar.scss create mode 100644 packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-icon/app-search-icon.component.tsx create mode 100644 packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-icon/app-search-icon.scss create mode 100644 packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-overlay/app-search-overlay.component.tsx diff --git a/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-bar/app-search-bar.component.tsx b/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-bar/app-search-bar.component.tsx new file mode 100644 index 000000000..18fb855f5 --- /dev/null +++ b/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-bar/app-search-bar.component.tsx @@ -0,0 +1,102 @@ +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Search } from '@carbon/react'; +import styles from './app-search-bar.scss'; + +import { type AssignedExtension, Extension, useConnectedExtensions } from '@openmrs/esm-framework'; +import { ComponentContext } from '@openmrs/esm-framework/src/internal'; + +const appMenuItemSlot = 'app-menu-item-slot'; + +interface AppSearchBarProps { + onChange?: (searchTerm: string) => void; + onClear: () => void; + onSubmit: (searchTerm: string) => void; + small?: boolean; +} + +const AppSearchBar = React.forwardRef( + ({ onChange, onClear, onSubmit, small }, ref) => { + const { t } = useTranslation(); + const [searchTerm, setSearchTerm] = useState(''); + const menuItemExtensions = useConnectedExtensions(appMenuItemSlot) as AssignedExtension[]; + + const handleChange = (val: string) => { + setSearchTerm(val); + if (onChange) { + onChange(val); + } + }; + + const handleSubmit = (evt: React.FormEvent) => { + evt.preventDefault(); + if (onSubmit) { + onSubmit(searchTerm); + } + }; + + const filteredExtensions = menuItemExtensions + .filter((extension) => { + const itemName = extension?.name ?? ''; + return itemName.toLowerCase().includes(searchTerm.toLowerCase()); + }) + .map((extension) => ( + + + + )); + + return ( + <> +
+ handleChange(event.target.value)} + onClear={onClear} + placeholder={t('searchForApp', 'Search for an application')} + size={small ? 'sm' : 'lg'} + value={searchTerm} + ref={ref} + data-testid="appSearchBar" + /> + +
+ {searchTerm + ? filteredExtensions + : menuItemExtensions.map((extension) => ( + + + + ))} +
+ + ); + }, +); + +export default AppSearchBar; diff --git a/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-bar/app-search-bar.scss b/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-bar/app-search-bar.scss new file mode 100644 index 000000000..6db8ccb64 --- /dev/null +++ b/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-bar/app-search-bar.scss @@ -0,0 +1,13 @@ +@use '@carbon/styles/scss/colors'; +@use '@carbon/styles/scss/spacing'; +@import '~@openmrs/esm-styleguide/src/vars'; +.appSearchInput { + border: none; +} + +.searchItems { + display: flex; + flex-wrap: wrap; + gap: 1rem; + margin: 0.5rem 0 0 0.5rem; +} diff --git a/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-icon/app-search-icon.component.tsx b/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-icon/app-search-icon.component.tsx new file mode 100644 index 000000000..180b5baf2 --- /dev/null +++ b/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-icon/app-search-icon.component.tsx @@ -0,0 +1,78 @@ +import React, { useCallback, useState, useEffect, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { HeaderGlobalAction } from '@carbon/react'; +import { Close, Switcher } from '@carbon/react/icons'; +import { isDesktop, navigate, useLayoutType, useOnClickOutside } from '@openmrs/esm-framework'; +import AppSearchOverlay from '../app-search-overlay/app-search-overlay.component'; +import styles from './app-search-icon.scss'; +import { useParams, useSearchParams } from 'react-router-dom'; + +interface AppSearchLaunchProps {} + +const AppSearchLaunch: React.FC = () => { + const { t } = useTranslation(); + const layout = useLayoutType(); + const { page } = useParams(); + const isSearchPage = useMemo(() => page === 'search', [page]); + const [searchParams] = useSearchParams(); + const initialSearchTerm = isSearchPage ? searchParams.get('query') : ''; + + const [showSearchInput, setShowSearchInput] = useState(false); + const [canClickOutside, setCanClickOutside] = useState(false); + + const handleCloseSearchInput = useCallback(() => { + if (isDesktop(layout) && !isSearchPage) { + setShowSearchInput(false); + } + }, [setShowSearchInput, isSearchPage, layout]); + + const ref = useOnClickOutside(handleCloseSearchInput, canClickOutside); + + const handleGlobalAction = useCallback(() => { + if (showSearchInput) { + if (isSearchPage) { + navigate({ + to: window.sessionStorage.getItem('searchReturnUrl') ?? '${openmrsSpaBase}/', + }); + window.sessionStorage.removeItem('searchReturnUrl'); + } + setShowSearchInput(false); + } else { + setShowSearchInput(true); + } + }, [isSearchPage, setShowSearchInput, showSearchInput]); + + useEffect(() => { + // Search input should always be open when we direct to the search page. + setShowSearchInput(isSearchPage); + if (isSearchPage) { + setCanClickOutside(false); + } + }, [isSearchPage]); + + useEffect(() => { + showSearchInput ? setCanClickOutside(true) : setCanClickOutside(false); + }, [showSearchInput]); + + return ( +
+ {showSearchInput && } + +
+ + {showSearchInput ? : } + +
+
+ ); +}; + +export default AppSearchLaunch; diff --git a/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-icon/app-search-icon.scss b/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-icon/app-search-icon.scss new file mode 100644 index 000000000..91a3d1a66 --- /dev/null +++ b/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-icon/app-search-icon.scss @@ -0,0 +1,15 @@ +@import '~@openmrs/esm-styleguide/src/vars'; + +.appSearchIconWrapper { + display: flex; + justify-content: flex-end; + align-items: center; +} + +.searchIconButton { + @include brand-01(background-color); +} + +.activeSearchIconButton { + @include brand-02(background-color); +} diff --git a/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-overlay/app-search-overlay.component.tsx b/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-overlay/app-search-overlay.component.tsx new file mode 100644 index 000000000..6b0575cef --- /dev/null +++ b/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-overlay/app-search-overlay.component.tsx @@ -0,0 +1,30 @@ +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import AppSearchBar from '../app-search-bar/app-search-bar.component'; +import debounce from 'lodash-es/debounce'; + +interface AppSearchOverlayProps { + onClose: () => void; + query?: string; + header?: string; +} + +const AppSearchOverlay: React.FC = ({ onClose, query = '', header }) => { + const { t } = useTranslation(); + const [searchTerm, setSearchTerm] = useState(query); + const handleClear = useCallback(() => setSearchTerm(''), [setSearchTerm]); + + useEffect(() => { + if (query) { + setSearchTerm(query); + } + }, [query]); + + const onSearchQueryChange = debounce((val) => { + setSearchTerm(val); + }, 300); + + return ; +}; + +export default AppSearchOverlay; From 584104c3e37d94be3c0c7ba4ee2c357a07eaf644 Mon Sep 17 00:00:00 2001 From: jabahum Date: Mon, 16 Sep 2024 15:51:03 +0300 Subject: [PATCH 2/3] chore --- .../app-menu/app-search-bar/app-search-bar.component.tsx | 2 +- .../app-search-overlay/app-search-overlay.component.tsx | 2 +- .../src/components/navbar/navbar.component.tsx | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-bar/app-search-bar.component.tsx b/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-bar/app-search-bar.component.tsx index 18fb855f5..663198dd5 100644 --- a/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-bar/app-search-bar.component.tsx +++ b/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-bar/app-search-bar.component.tsx @@ -67,7 +67,7 @@ const AppSearchBar = React.forwardRef( labelText="" onChange={(event) => handleChange(event.target.value)} onClear={onClear} - placeholder={t('searchForApp', 'Search for an application')} + placeholder={t('searchForModule', 'Search for module')} size={small ? 'sm' : 'lg'} value={searchTerm} ref={ref} diff --git a/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-overlay/app-search-overlay.component.tsx b/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-overlay/app-search-overlay.component.tsx index 6b0575cef..41187a9b3 100644 --- a/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-overlay/app-search-overlay.component.tsx +++ b/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-overlay/app-search-overlay.component.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import AppSearchBar from '../app-search-bar/app-search-bar.component'; import debounce from 'lodash-es/debounce'; diff --git a/packages/apps/esm-primary-navigation-app/src/components/navbar/navbar.component.tsx b/packages/apps/esm-primary-navigation-app/src/components/navbar/navbar.component.tsx index cdc57d425..b5f21ad98 100644 --- a/packages/apps/esm-primary-navigation-app/src/components/navbar/navbar.component.tsx +++ b/packages/apps/esm-primary-navigation-app/src/components/navbar/navbar.component.tsx @@ -22,6 +22,7 @@ import OfflineBanner from '../offline-banner/offline-banner.component'; import UserMenuPanel from '../navbar-header-panels/user-menu-panel.component'; import SideMenuPanel from '../navbar-header-panels/side-menu-panel.component'; import styles from './navbar.scss'; +import AppSearchLaunch from '../app-menu/app-search-icon/app-search-icon.component'; const HeaderItems: React.FC = () => { const { t } = useTranslation(); @@ -115,7 +116,7 @@ const HeaderItems: React.FC = () => { )} {!isDesktop(layout) && } - {showAppMenu && } + {showAppMenu && } {showUserMenu && } From d25dd345dee03e4f0cca73f3fd6212307333d90c Mon Sep 17 00:00:00 2001 From: jabahum Date: Mon, 16 Sep 2024 15:52:47 +0300 Subject: [PATCH 3/3] fix translation --- .../app-search-icon/app-search-icon.component.tsx | 8 ++++---- .../apps/esm-primary-navigation-app/translations/en.json | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-icon/app-search-icon.component.tsx b/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-icon/app-search-icon.component.tsx index 180b5baf2..960b32024 100644 --- a/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-icon/app-search-icon.component.tsx +++ b/packages/apps/esm-primary-navigation-app/src/components/app-menu/app-search-icon/app-search-icon.component.tsx @@ -60,12 +60,12 @@ const AppSearchLaunch: React.FC = () => {
{showSearchInput ? : } diff --git a/packages/apps/esm-primary-navigation-app/translations/en.json b/packages/apps/esm-primary-navigation-app/translations/en.json index 84f2bddaa..afa16a429 100644 --- a/packages/apps/esm-primary-navigation-app/translations/en.json +++ b/packages/apps/esm-primary-navigation-app/translations/en.json @@ -3,6 +3,9 @@ "change": "Change", "changeLanguage": "Change language", "changingLanguage": "Changing language", + "clearSearch": "Clear", "notifications": "Notifications", + "searchApp": "Search App", + "searchForModule": "Search for module", "userMenuTooltip": "My Account" }