{isHeaderVisible && (
<>
{
const currentRef = useRef(null);
const dispatch = useDispatch();
+ const [searchParams] = useSearchParams();
+ const locatorId = searchParams.get('show');
+ const isScrolledToElement = locatorId === unit.id;
const [isFormOpen, openForm, closeForm] = useToggle(false);
const namePrefix = 'unit';
@@ -109,10 +113,10 @@ const UnitCard = ({
// if this items has been newly added, scroll to it.
// we need to check section.shouldScroll as whole section is fetched when a
// unit is duplicated under it.
- if (currentRef.current && (section.shouldScroll || unit.shouldScroll)) {
+ if (currentRef.current && (section.shouldScroll || unit.shouldScroll || isScrolledToElement)) {
scrollToElement(currentRef.current);
}
- }, []);
+ }, [isScrolledToElement]);
useEffect(() => {
if (savingStatus === RequestStatus.SUCCESSFUL) {
@@ -139,7 +143,7 @@ const UnitCard = ({
}}
>
diff --git a/src/search-modal/BlockTypeLabel.jsx b/src/search-modal/BlockTypeLabel.jsx
index 8a6048df47..8eb41f613b 100644
--- a/src/search-modal/BlockTypeLabel.jsx
+++ b/src/search-modal/BlockTypeLabel.jsx
@@ -19,7 +19,7 @@ const BlockTypeLabel = ({ type }) => {
// Replace underscores and hypens with spaces, then let the browser capitalize this
// in a locale-aware way to get a reasonable display value.
// e.g. 'drag-and-drop-v2' -> "Drag And Drop V2"
- return {type.replace(/[_-]/g, ' ')};
+ return XXX {type.replace(/[_-]/g, ' ')};
};
export default BlockTypeLabel;
diff --git a/src/search-modal/EmptyStates.jsx b/src/search-modal/EmptyStates.jsx
index a90a294df2..a1714b1da5 100644
--- a/src/search-modal/EmptyStates.jsx
+++ b/src/search-modal/EmptyStates.jsx
@@ -1,8 +1,30 @@
/* eslint-disable react/prop-types */
// @ts-check
import React from 'react';
+import { FormattedMessage } from '@edx/frontend-platform/i18n';
+import { Stack } from '@openedx/paragon';
import { useStats, useClearRefinements } from 'react-instantsearch';
+import EmptySearchImage from './images/empty-search.svg';
+import NoResultImage from './images/no-results.svg';
+import messages from './messages';
+
+const EmptySearch = () => (
+
+
+
+
+
+);
+
+const NoResults = () => (
+
+
+
+
+
+);
+
/**
* If the user hasn't put any keywords/filters yet, display an "empty state".
* Likewise, if the results are empty (0 results), display a special message.
@@ -16,12 +38,10 @@ const EmptyStates = ({ children }) => {
if (!hasQuery && !hasFiltersApplied) {
// We haven't started the search yet. Display the "start your search" empty state
- // Note this isn't localized because it's going to be replaced in a fast-follow PR.
- return
Enter a keyword or select a filter to begin searching.
;
+ return ;
}
if (nbHits === 0) {
- // Note this isn't localized because it's going to be replaced in a fast-follow PR.
- return
No results found. Change your search and try again.
;
+ return ;
}
return children;
diff --git a/src/search-modal/SearchEndpointLoader.jsx b/src/search-modal/SearchEndpointLoader.jsx
index 664a3b5e03..3c5ab7f443 100644
--- a/src/search-modal/SearchEndpointLoader.jsx
+++ b/src/search-modal/SearchEndpointLoader.jsx
@@ -10,8 +10,8 @@ import { useContentSearch } from './data/apiHooks';
import SearchUI from './SearchUI';
import messages from './messages';
-/** @type {React.FC<{courseId: string}>} */
-const SearchEndpointLoader = ({ courseId }) => {
+/** @type {React.FC<{courseId: string, closeSearch: () => void}>} */
+const SearchEndpointLoader = ({ courseId, closeSearch }) => {
const intl = useIntl();
// Load the Meilisearch connection details from the LMS: the URL to use, the index name, and an API key specific
@@ -25,7 +25,7 @@ const SearchEndpointLoader = ({ courseId }) => {
const title = intl.formatMessage(messages.title);
if (searchEndpointData) {
- return ;
+ return ;
}
return (
<>
diff --git a/src/search-modal/SearchModal.jsx b/src/search-modal/SearchModal.jsx
index 93fce12720..d7cd9ea91b 100644
--- a/src/search-modal/SearchModal.jsx
+++ b/src/search-modal/SearchModal.jsx
@@ -1,8 +1,8 @@
/* eslint-disable react/prop-types */
// @ts-check
import React from 'react';
-import { ModalDialog } from '@openedx/paragon';
import { useIntl } from '@edx/frontend-platform/i18n';
+import { ModalDialog } from '@openedx/paragon';
import SearchEndpointLoader from './SearchEndpointLoader';
import messages from './messages';
@@ -24,7 +24,7 @@ const SearchModal = ({ courseId, ...props }) => {
isFullscreenOnMobile
className="courseware-search-modal"
>
-
+
);
};
diff --git a/src/search-modal/SearchModal.scss b/src/search-modal/SearchModal.scss
index c67c6ba2a9..3054909dab 100644
--- a/src/search-modal/SearchModal.scss
+++ b/src/search-modal/SearchModal.scss
@@ -68,4 +68,14 @@
.ais-InfiniteHits-loadMore--disabled {
display: none; // temporary; remove this once we implement our own / component.
}
+
+ .search-result {
+ &:hover {
+ background-color: $gray-100 !important;
+ }
+ }
+
+ .fs-large {
+ font-size: $font-size-base * 1.25;
+ }
}
diff --git a/src/search-modal/SearchResult.jsx b/src/search-modal/SearchResult.jsx
index cb28172eac..db6f39b386 100644
--- a/src/search-modal/SearchResult.jsx
+++ b/src/search-modal/SearchResult.jsx
@@ -1,37 +1,151 @@
/* eslint-disable react/prop-types */
// @ts-check
import React from 'react';
-import { Highlight } from 'react-instantsearch';
-import BlockTypeLabel from './BlockTypeLabel';
+import { getConfig, getPath } from '@edx/frontend-platform';
+import {
+ Icon,
+ IconButton,
+ Stack,
+} from '@openedx/paragon';
+import {
+ Article,
+ Folder,
+ OpenInNew,
+ Question,
+ TextFields,
+ Videocam,
+} from '@openedx/paragon/icons';
+import {
+ Highlight,
+ Snippet,
+} from 'react-instantsearch';
+import { useSelector } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
+
+import {
+ getLoadingStatuses,
+ getSavingStatuses,
+ getStudioHomeData,
+} from '../studio-home/data/selectors';
/**
- * A single search result (row), usually represents an XBlock/Component
- * @type {React.FC<{hit: import('instantsearch.js').Hit<{
- * id: string,
- * display_name: string,
- * block_type: string,
- * 'content.html_content'?: string,
- * 'content.capa_content'?: string,
- * breadcrumbs: {display_name: string}[]}>,
+ * @typedef {import('instantsearch.js').Hit<{
+ * id: string,
+ * usage_key: string,
+ * context_key: string,
+ * display_name: string,
+ * block_type: string,
+ * 'content.html_content'?: string,
+ * 'content.capa_content'?: string,
+ * breadcrumbs: {display_name: string}[]
+ * breadcrumbsNames: string[],
+ * }>} CustomHit
+ */
+
+/**
+ * Custom Highlight component that uses the tag for highlighting
+ * @type {React.FC<{
+ * attribute: keyof CustomHit | string[],
+ * hit: CustomHit,
+ * separator?: string,
* }>}
*/
-const SearchResult = ({ hit }) => (
-