diff --git a/src/assets/scss/_utilities.scss b/src/assets/scss/_utilities.scss
index 441fe804d..6e74efb45 100644
--- a/src/assets/scss/_utilities.scss
+++ b/src/assets/scss/_utilities.scss
@@ -1,3 +1,7 @@
.text-black {
color: $black;
}
+
+.mw-300px {
+ max-width: 300px;
+}
diff --git a/src/generic/sub-header/SubHeader.jsx b/src/generic/sub-header/SubHeader.jsx
index a0146fcd5..3f166ac2d 100644
--- a/src/generic/sub-header/SubHeader.jsx
+++ b/src/generic/sub-header/SubHeader.jsx
@@ -18,7 +18,7 @@ const SubHeader = ({
{subtitle}
{title}
{titleActions && (
-
+
{titleActions}
)}
diff --git a/src/index.jsx b/src/index.jsx
index 37c0e9b6f..7ef042750 100755
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -7,7 +7,9 @@ import {
import { AppProvider, ErrorPage } from '@edx/frontend-platform/react';
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
-import { Navigate, Route, Routes } from 'react-router-dom';
+import {
+ Navigate, Route, createRoutesFromElements, createBrowserRouter, RouterProvider,
+} from 'react-router-dom';
import {
QueryClient,
QueryClientProvider,
@@ -45,31 +47,37 @@ const App = () => {
}
}, []);
+ const router = createBrowserRouter(
+ createRoutesFromElements(
+
+ } />
+ } />
+ } />
+ {process.env.ENABLE_TAGGING_TAXONOMY_PAGES === 'true' && (
+ <>
+ {/* TODO: remove this redirect once Studio's link is updated */}
+ } />
+ }>
+ } />
+
+ }>
+ } />
+
+ }
+ />
+ >
+ )}
+ ,
+ ),
+ );
+
return (
-
+
-
- } />
- } />
- } />
- {process.env.ENABLE_TAGGING_TAXONOMY_PAGES === 'true' && (
- <>
- {/* TODO: remove this redirect once Studio's link is updated */}
- } />
- }>
- } />
-
- }>
- } />
-
- }
- />
- >
- )}
-
+
);
diff --git a/src/taxonomy/TaxonomyLayout.jsx b/src/taxonomy/TaxonomyLayout.jsx
index c49c55ee8..093ee743d 100644
--- a/src/taxonomy/TaxonomyLayout.jsx
+++ b/src/taxonomy/TaxonomyLayout.jsx
@@ -1,6 +1,6 @@
import React, { useMemo, useState } from 'react';
import { StudioFooter } from '@edx/frontend-component-footer';
-import { Outlet } from 'react-router-dom';
+import { Outlet, ScrollRestoration } from 'react-router-dom';
import { Toast } from '@edx/paragon';
import Header from '../header';
@@ -28,6 +28,7 @@ const TaxonomyLayout = () => {
{toastMessage}
+
);
};
diff --git a/src/taxonomy/TaxonomyLayout.test.jsx b/src/taxonomy/TaxonomyLayout.test.jsx
index d83380999..aeece7092 100644
--- a/src/taxonomy/TaxonomyLayout.test.jsx
+++ b/src/taxonomy/TaxonomyLayout.test.jsx
@@ -16,6 +16,7 @@ jest.mock('@edx/frontend-component-footer', () => ({
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
Outlet: jest.fn(() => ),
+ ScrollRestoration: jest.fn(() => ),
}));
jest.mock('react', () => ({
...jest.requireActual('react'),
diff --git a/src/taxonomy/TaxonomyListPage.jsx b/src/taxonomy/TaxonomyListPage.jsx
index 8f991fbfd..c80a39332 100644
--- a/src/taxonomy/TaxonomyListPage.jsx
+++ b/src/taxonomy/TaxonomyListPage.jsx
@@ -114,17 +114,22 @@ const TaxonomyListPage = () => {
div {
+ // Used to extend the clickable area to all the width of the modal
+ width: 100%;
+ }
+}
diff --git a/src/taxonomy/export-modal/index.jsx b/src/taxonomy/export-modal/index.jsx
index 21ceedfe4..ebc8def52 100644
--- a/src/taxonomy/export-modal/index.jsx
+++ b/src/taxonomy/export-modal/index.jsx
@@ -31,6 +31,7 @@ const ExportModal = ({
size="lg"
hasCloseButton
isFullscreenOnMobile
+ className="taxonomy-export-modal"
>
diff --git a/src/taxonomy/index.scss b/src/taxonomy/index.scss
index be1464fd9..3655a35bc 100644
--- a/src/taxonomy/index.scss
+++ b/src/taxonomy/index.scss
@@ -1,2 +1,4 @@
@import "taxonomy/taxonomy-card/TaxonomyCard";
@import "taxonomy/delete-dialog/DeleteDialog";
+@import "taxonomy/system-defined-badge/SystemDefinedBadge";
+@import "taxonomy/export-modal/ExportModal";
diff --git a/src/taxonomy/system-defined-badge/SystemDefinedBadge.scss b/src/taxonomy/system-defined-badge/SystemDefinedBadge.scss
new file mode 100644
index 000000000..dfab7827f
--- /dev/null
+++ b/src/taxonomy/system-defined-badge/SystemDefinedBadge.scss
@@ -0,0 +1,3 @@
+.system-defined-badge {
+ font-size: 12px;
+}
diff --git a/src/taxonomy/system-defined-badge/index.jsx b/src/taxonomy/system-defined-badge/index.jsx
new file mode 100644
index 000000000..af4937342
--- /dev/null
+++ b/src/taxonomy/system-defined-badge/index.jsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { useIntl } from '@edx/frontend-platform/i18n';
+import {
+ Badge,
+ OverlayTrigger,
+ Popover,
+} from '@edx/paragon';
+import messages from './messages';
+
+const SystemDefinedBadge = ({ taxonomyId }) => {
+ const intl = useIntl();
+ const getToolTip = () => (
+
+
+ {intl.formatMessage(messages.systemTaxonomyPopoverTitle)}
+
+
+ {intl.formatMessage(messages.systemTaxonomyPopoverBody)}
+
+
+ );
+
+ return (
+
+
+ {intl.formatMessage(messages.systemDefinedBadge)}
+
+
+ );
+};
+
+SystemDefinedBadge.propTypes = {
+ taxonomyId: PropTypes.number.isRequired,
+};
+
+export default SystemDefinedBadge;
diff --git a/src/taxonomy/system-defined-badge/messages.js b/src/taxonomy/system-defined-badge/messages.js
new file mode 100644
index 000000000..59642d1e5
--- /dev/null
+++ b/src/taxonomy/system-defined-badge/messages.js
@@ -0,0 +1,18 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+
+const messages = defineMessages({
+ systemTaxonomyPopoverTitle: {
+ id: 'course-authoring.taxonomy-list.popover.system-defined.title',
+ defaultMessage: 'System taxonomy',
+ },
+ systemTaxonomyPopoverBody: {
+ id: 'course-authoring.taxonomy-list.popover.system-defined.body',
+ defaultMessage: 'This is a system-level taxonomy and is enabled by default.',
+ },
+ systemDefinedBadge: {
+ id: 'course-authoring.taxonomy-list.badge.system-defined.label',
+ defaultMessage: 'System-level',
+ },
+});
+
+export default messages;
diff --git a/src/taxonomy/tag-list/TagListTable.jsx b/src/taxonomy/tag-list/TagListTable.jsx
index 2fa3c042a..1e2638fae 100644
--- a/src/taxonomy/tag-list/TagListTable.jsx
+++ b/src/taxonomy/tag-list/TagListTable.jsx
@@ -24,7 +24,7 @@ const SubTagsExpanded = ({ taxonomyId, parentTagValue }) => {
{subTagsData.data.results.map(tagData => (
-
- {tagData.value} {tagData.childCount > 0 ? `(${tagData.childCount})` : null}
+ {tagData.value} {tagData.childCount > 0 ? `(${tagData.childCount})` : null}
))}
@@ -39,9 +39,20 @@ SubTagsExpanded.propTypes = {
/**
* An "Expand" toggle to show/hide subtags, but one which is hidden if the given tag row has no subtags.
*/
-const OptionalExpandLink = ({ row }) => (row.values.childCount > 0 ? : null);
+const OptionalExpandLink = ({ row }) => (row.original.childCount > 0 ? : null);
OptionalExpandLink.propTypes = DataTable.ExpandRow.propTypes;
+/**
+ * Custom DataTable cell to join tag value with child count
+ */
+const TagValue = ({ row }) => (
+ <>
+ {row.original.value}
+ {` (${row.original.childCount})`}
+ >
+);
+TagValue.propTypes = DataTable.TableCell.propTypes;
+
const TagListTable = ({ taxonomyId }) => {
const intl = useIntl();
const [options, setOptions] = useState({
@@ -69,21 +80,19 @@ const TagListTable = ({ taxonomyId }) => {
isExpandable
// This is a temporary "bare bones" solution for brute-force loading all the child tags. In future we'll match
// the Figma design and do something more sophisticated.
- renderRowSubComponent={({ row }) => }
+ renderRowSubComponent={({ row }) => (
+
+ )}
columns={[
{
Header: intl.formatMessage(messages.tagListColumnValueHeader),
- accessor: 'value',
+ Cell: TagValue,
},
{
id: 'expander',
Header: DataTable.ExpandAll,
Cell: OptionalExpandLink,
},
- {
- Header: intl.formatMessage(messages.tagListColumnChildCountHeader),
- accessor: 'childCount',
- },
]}
>
diff --git a/src/taxonomy/tag-list/TagListTable.test.jsx b/src/taxonomy/tag-list/TagListTable.test.jsx
index dd79c107f..f2d850205 100644
--- a/src/taxonomy/tag-list/TagListTable.test.jsx
+++ b/src/taxonomy/tag-list/TagListTable.test.jsx
@@ -77,7 +77,7 @@ const subTagsResponse = {
};
const subTagsUrl = 'http://localhost:18010/api/content_tagging/v1/taxonomies/1/tags/?full_depth_threshold=10000&parent_tag=two+level+tag+1';
-describe('', () => {
+describe('', () => {
beforeAll(async () => {
initializeMockApp({
authenticatedUser: {
diff --git a/src/taxonomy/tag-list/messages.js b/src/taxonomy/tag-list/messages.js
index 82db9caba..77c0efa11 100644
--- a/src/taxonomy/tag-list/messages.js
+++ b/src/taxonomy/tag-list/messages.js
@@ -7,11 +7,7 @@ const messages = defineMessages({
},
tagListColumnValueHeader: {
id: 'course-authoring.tag-list.column.value.header',
- defaultMessage: 'Value',
- },
- tagListColumnChildCountHeader: {
- id: 'course-authoring.tag-list.column.value.header',
- defaultMessage: '# child tags',
+ defaultMessage: 'Tag name',
},
tagListError: {
id: 'course-authoring.tag-list.error',
diff --git a/src/taxonomy/taxonomy-card/TaxonomyCard.scss b/src/taxonomy/taxonomy-card/TaxonomyCard.scss
index b4839bd1c..d124b95e8 100644
--- a/src/taxonomy/taxonomy-card/TaxonomyCard.scss
+++ b/src/taxonomy/taxonomy-card/TaxonomyCard.scss
@@ -23,13 +23,6 @@
-webkit-line-clamp: 6;
}
- .pgn__card-header-title-md {
- /* Set overflow to title of the card */
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
-
.taxonomy-menu-item:focus {
/**
* There is a bug in the menu that auto focus the first item.
diff --git a/src/taxonomy/taxonomy-card/index.jsx b/src/taxonomy/taxonomy-card/index.jsx
index 2b9476355..2f640cbf3 100644
--- a/src/taxonomy/taxonomy-card/index.jsx
+++ b/src/taxonomy/taxonomy-card/index.jsx
@@ -1,18 +1,18 @@
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
import {
- Badge,
Card,
OverlayTrigger,
Popover,
} from '@edx/paragon';
import PropTypes from 'prop-types';
-import { Link } from 'react-router-dom';
+import { NavLink } from 'react-router-dom';
import classNames from 'classnames';
import { useIntl } from '@edx/frontend-platform/i18n';
import messages from './messages';
import TaxonomyCardMenu from './TaxonomyCardMenu';
import ExportModal from '../export-modal';
import DeleteDialog from '../delete-dialog';
+import SystemDefinedBadge from '../system-defined-badge';
const orgsCountEnabled = (orgsCount) => orgsCount !== undefined && orgsCount !== 0;
@@ -20,30 +20,10 @@ const HeaderSubtitle = ({
id, showSystemBadge, orgsCount,
}) => {
const intl = useIntl();
- const getSystemToolTip = () => (
-
-
- {intl.formatMessage(messages.systemTaxonomyPopoverTitle)}
-
-
- {intl.formatMessage(messages.systemTaxonomyPopoverBody)}
-
-
- );
// Show system defined badge
if (showSystemBadge) {
- return (
-
-
- {intl.formatMessage(messages.systemDefinedBadge)}
-
-
- );
+ return ;
}
// Or show orgs count
@@ -59,10 +39,55 @@ const HeaderSubtitle = ({
return null;
};
+HeaderSubtitle.defaultProps = {
+ orgsCount: undefined,
+};
+
HeaderSubtitle.propTypes = {
id: PropTypes.number.isRequired,
showSystemBadge: PropTypes.bool.isRequired,
- orgsCount: PropTypes.number.isRequired,
+ orgsCount: PropTypes.number,
+};
+
+const HeaderTitle = ({ taxonomyId, title }) => {
+ const containerRef = useRef(null);
+ const textRef = useRef(null);
+ const [isTruncated, setIsTruncated] = useState(false);
+
+ useEffect(() => {
+ const containerWidth = containerRef.current.clientWidth;
+ const textWidth = textRef.current.offsetWidth;
+ setIsTruncated(textWidth > containerWidth);
+ }, [title]);
+
+ const getToolTip = () => (
+
+
+ {title}
+
+
+ );
+
+ return (
+
+
+ {title}
+
+
+ );
+};
+
+HeaderTitle.propTypes = {
+ taxonomyId: PropTypes.number.isRequired,
+ title: PropTypes.string.isRequired,
};
const TaxonomyCard = ({ className, original, onDeleteTaxonomy }) => {
@@ -135,13 +160,13 @@ const TaxonomyCard = ({ className, original, onDeleteTaxonomy }) => {
<>
}
subtitle={(
{
const intl = useIntl();
@@ -104,6 +105,13 @@ const TaxonomyDetailPage = () => {
);
};
+ const getSystemDefinedBadge = () => {
+ if (taxonomy.systemDefined) {
+ return ;
+ }
+ return null;
+ };
+
return (
<>
@@ -120,6 +128,7 @@ const TaxonomyDetailPage = () => {
/>
diff --git a/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.test.jsx b/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.test.jsx
index bd95a7500..5cd657153 100644
--- a/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.test.jsx
+++ b/src/taxonomy/taxonomy-detail/TaxonomyDetailPage.test.jsx
@@ -95,6 +95,22 @@ describe('', async () => {
expect(getByRole('heading')).toHaveTextContent('Test taxonomy');
});
+ it('should show system defined badge', async () => {
+ useTaxonomyDetailData.mockReturnValue({
+ isSuccess: true,
+ isFetched: true,
+ isError: false,
+ data: {
+ id: 1,
+ name: 'Test taxonomy',
+ description: 'This is a description',
+ systemDefined: true,
+ },
+ });
+ const { getByText } = render();
+ expect(getByText('System-level')).toBeInTheDocument();
+ });
+
it('should open export modal on export menu click', () => {
useTaxonomyDetailData.mockReturnValue({
isSuccess: true,