diff --git a/src/course-tabs/CourseTabLink.tsx b/src/course-tabs/CourseTabLink.tsx
new file mode 100644
index 0000000000..16ccdce4e8
--- /dev/null
+++ b/src/course-tabs/CourseTabLink.tsx
@@ -0,0 +1,20 @@
+import classNames from 'classnames';
+import React from 'react';
+
+interface CourseTabLinkProps {
+ slug: string;
+ activeTabSlug?: string;
+ url: string;
+ title: string;
+}
+
+export const CourseTabLink = ({
+ slug, activeTabSlug, url, title,
+}: CourseTabLinkProps) => (
+
+ {title}
+
+);
diff --git a/src/course-tabs/CourseTabLinksList.tsx b/src/course-tabs/CourseTabLinksList.tsx
new file mode 100644
index 0000000000..894debcd77
--- /dev/null
+++ b/src/course-tabs/CourseTabLinksList.tsx
@@ -0,0 +1,25 @@
+import { CourseTabLink } from '@src/course-tabs/CourseTabLink';
+import React from 'react';
+
+interface CourseTabLinkListProps {
+ tabs: Array<{
+ title: string;
+ slug: string;
+ url: string;
+ }>,
+ activeTabSlug?: string;
+}
+
+export const CourseTabLinksList = ({ tabs, activeTabSlug }: CourseTabLinkListProps) => (
+ <>
+ {tabs.map(({ url, title, slug }) => (
+
+ ))}
+ >
+);
diff --git a/src/course-tabs/CourseTabsNavigation.jsx b/src/course-tabs/CourseTabsNavigation.tsx
similarity index 58%
rename from src/course-tabs/CourseTabsNavigation.jsx
rename to src/course-tabs/CourseTabsNavigation.tsx
index 9c2a12ef8c..87a1b92c4a 100644
--- a/src/course-tabs/CourseTabsNavigation.jsx
+++ b/src/course-tabs/CourseTabsNavigation.tsx
@@ -1,16 +1,28 @@
import React from 'react';
-import PropTypes from 'prop-types';
-import { useIntl } from '@edx/frontend-platform/i18n';
import classNames from 'classnames';
-
-import messages from './messages';
-import Tabs from '../generic/tabs/Tabs';
+import { useIntl } from '@edx/frontend-platform/i18n';
+import { CourseTabLinksSlot } from '../plugin-slots/CourseTabLinksSlot';
import { CoursewareSearch, CoursewareSearchToggle } from '../course-home/courseware-search';
import { useCoursewareSearchState } from '../course-home/courseware-search/hooks';
+import Tabs from '../generic/tabs/Tabs';
+import messages from './messages';
+
+interface CourseTabsNavigationProps {
+ activeTabSlug?: string;
+ className?: string | null;
+ tabs: Array<{
+ title: string;
+ slug: string;
+ url: string;
+ }>;
+}
+
const CourseTabsNavigation = ({
- activeTabSlug, className, tabs,
-}) => {
+ activeTabSlug = undefined,
+ className = null,
+ tabs,
+}:CourseTabsNavigationProps) => {
const intl = useIntl();
const { show } = useCoursewareSearchState();
@@ -23,15 +35,7 @@ const CourseTabsNavigation = ({
className="nav-underline-tabs"
aria-label={intl.formatMessage(messages.courseMaterial)}
>
- {tabs.map(({ url, title, slug }) => (
-
- {title}
-
- ))}
+
@@ -44,19 +48,4 @@ const CourseTabsNavigation = ({
);
};
-CourseTabsNavigation.propTypes = {
- activeTabSlug: PropTypes.string,
- className: PropTypes.string,
- tabs: PropTypes.arrayOf(PropTypes.shape({
- title: PropTypes.string.isRequired,
- slug: PropTypes.string.isRequired,
- url: PropTypes.string.isRequired,
- })).isRequired,
-};
-
-CourseTabsNavigation.defaultProps = {
- activeTabSlug: undefined,
- className: null,
-};
-
export default CourseTabsNavigation;
diff --git a/src/plugin-slots/CourseTabLinksSlot/README.md b/src/plugin-slots/CourseTabLinksSlot/README.md
new file mode 100644
index 0000000000..653086e248
--- /dev/null
+++ b/src/plugin-slots/CourseTabLinksSlot/README.md
@@ -0,0 +1,51 @@
+# Course Tab Links Slot
+
+### Slot ID: `org.openedx.frontend.learning.course_tab_links.v1`
+
+### Props:
+* `activeTabSlug`: The slug of the currently active tab.
+
+## Description
+
+This slot is used to replace/modify/hide the course tabs.
+
+## Example
+
+### Added link to Course Tabs
+
+
+The following `env.config.jsx` will add a new course tab call "Custom Tab".
+
+```js
+import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework';
+
+import { CourseTabLink } from '@src/course-tabs/CourseTabLink';
+
+
+const config = {
+ pluginSlots: {
+ "org.openedx.frontend.learning.course_tab_links.v1": {
+ keepDefault: true,
+ plugins: [
+ {
+ op: PLUGIN_OPERATIONS.Insert,
+ widget: {
+ id: 'custom_tab',
+ type: DIRECT_PLUGIN,
+ RenderWidget: ({ activeTabSlug })=> (
+
+ ),
+ },
+ },
+ ],
+ },
+ },
+}
+
+export default config;
+```
diff --git a/src/plugin-slots/CourseTabLinksSlot/course-tabs-custom.png b/src/plugin-slots/CourseTabLinksSlot/course-tabs-custom.png
new file mode 100644
index 0000000000..6c9a4d68ac
Binary files /dev/null and b/src/plugin-slots/CourseTabLinksSlot/course-tabs-custom.png differ
diff --git a/src/plugin-slots/CourseTabLinksSlot/index.tsx b/src/plugin-slots/CourseTabLinksSlot/index.tsx
new file mode 100644
index 0000000000..2515399e08
--- /dev/null
+++ b/src/plugin-slots/CourseTabLinksSlot/index.tsx
@@ -0,0 +1,21 @@
+import { PluginSlot } from '@openedx/frontend-plugin-framework';
+import { CourseTabLinksList } from '@src/course-tabs/CourseTabLinksList';
+import React from 'react';
+
+type CourseTabList = Array<{
+ title: string;
+ slug: string;
+ url: string;
+}>;
+
+export const CourseTabLinksSlot = ({ tabs, activeTabSlug }: {
+ tabs: CourseTabList,
+ activeTabSlug?: string
+}) => (
+
+
+
+);