diff --git a/README.md b/README.md
index d351962f92..453b6d2bcb 100644
--- a/README.md
+++ b/README.md
@@ -514,10 +514,34 @@ Paragon components can have different behavior in the MFE environment. `example`
Steps to install the `example` app.
+To set up the example app with a local Tutor installation, you'll need to configure a Tutor plugin to handle CORS settings
+
+Prerequisites.
+
+Before running the example app, ensure you have a local Tutor installation configured.
+1. Create a new file (i.e. `paragon-example-app.py`) in your tutor plugins folder (`tutor plugins printroot` to get the path).
+2. Add the following content to the paragon-example-app.py file:
+```
+from tutor import hooks
+
+hooks.Filters.ENV_PATCHES.add_item(
+ (
+ "openedx-lms-development-settings",
+ """
+CORS_ORIGIN_WHITELIST.append("http://localhost:8080")
+LOGIN_REDIRECT_WHITELIST.append("http://localhost:8080")
+CSRF_TRUSTED_ORIGINS.append("http://localhost:8080")
+ """
+ )
+)
+```
+3. Enable the plugin: `tutor plugins enable paragon-example-app`
+4. Apply configuration changes: `tutor config save` && `tutor dev restart`
+
+Running the Example App:
1. `npm install` to install dependencies.
-2. Launch any devstack. It is required for MFE to login into the system and set up configs.
-3. `npm run start-example-mfe` to start the app.
-4. Go to the `example/src/MyComponent.jsx` and use Paragon components inside the MFE environment.
+2. `npm run example:start` to start the app.
+3. Go to the `example/src/MyComponent.jsx` and use Paragon components inside the MFE environment.
## Semantic Release
diff --git a/example/.env.development b/example/.env.development
index 34027f9730..7ff743a905 100644
--- a/example/.env.development
+++ b/example/.env.development
@@ -1,4 +1,8 @@
-NODE_ENV='development'
-REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
-LMS_BASE_URL='http://localhost:18000'
-ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
+NODE_ENV=development
+REFRESH_ACCESS_TOKEN_ENDPOINT=http://local.openedx.io:8000/login_refresh
+LMS_BASE_URL=http://local.openedx.io:8000
+ACCESS_TOKEN_COOKIE_NAME=edx-jwt-cookie-header-payload
+BASE_URL=http://local.openedx.io:8080
+LOGIN_URL=http://local.openedx.io:8000/login
+LOGOUT_URL=http://local.openedx.io:8000/logout
+CSRF_TOKEN_API_PATH=/csrf/api/v1/token
diff --git a/example/public/index.html b/example/public/index.html
index c00bf39063..4409e458f5 100644
--- a/example/public/index.html
+++ b/example/public/index.html
@@ -1,7 +1,7 @@
- Example
+ Paragon Example
diff --git a/example/src/index.jsx b/example/src/index.jsx
index 9ce67d7ced..10f45d271f 100644
--- a/example/src/index.jsx
+++ b/example/src/index.jsx
@@ -1,9 +1,8 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
+import { createRoot } from 'react-dom/client';
import {
AppProvider,
ErrorPage,
- PageRoute,
+ PageWrap,
} from '@edx/frontend-platform/react';
import { APP_INIT_ERROR, APP_READY, initialize } from '@edx/frontend-platform';
import { subscribe } from '@edx/frontend-platform/pubSub';
@@ -11,21 +10,21 @@ import MyComponent from './MyComponent';
import './index.scss';
+const container = document.getElementById('root');
+const root = createRoot(container);
+
subscribe(APP_READY, () => {
- ReactDOM.render(
+ root.render(
-
+
+
+ ,
- document.getElementById('root'),
);
});
subscribe(APP_INIT_ERROR, (error) => {
- ReactDOM.render(, document.getElementById('root'));
+ root.render();
});
initialize({
diff --git a/package-lock.json b/package-lock.json
index d11752622d..baefa5afd0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15585,9 +15585,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001760",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz",
- "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==",
+ "version": "1.0.30001764",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz",
+ "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==",
"funding": [
{
"type": "opencollective",
diff --git a/src/Breadcrumb/Breadcrumb.test.jsx b/src/Breadcrumb/Breadcrumb.test.tsx
similarity index 75%
rename from src/Breadcrumb/Breadcrumb.test.jsx
rename to src/Breadcrumb/Breadcrumb.test.tsx
index 3b402d971e..4f11c5afbb 100644
--- a/src/Breadcrumb/Breadcrumb.test.jsx
+++ b/src/Breadcrumb/Breadcrumb.test.tsx
@@ -95,4 +95,29 @@ describe('', () => {
expect(links.getAttribute('target')).toBe('_blank');
expect(links.getAttribute('href')).toBe('/link-1');
});
+
+ it('renders with a custom CSS class', () => {
+ render();
+ const nav = screen.getByLabelText('Breadcrumbs');
+ expect(nav.classList).toContain('foobar');
+ expect(nav.classList).toContain('pgn__breadcrumb');
+ expect(nav.classList).toContain('pgn__breadcrumb-light');
+ });
+
+ it('can access data-xxxxx attributes on the links in clickHandler', async () => {
+ const user = userEvent.setup();
+ const clickHandler = jest.fn();
+ render(
+ clickHandler(currentTarget.dataset.parentIndex)}
+ />,
+ );
+ const link = screen.getByRole('link', { name: 'Link1' });
+ await user.click(link);
+ expect(clickHandler).toHaveBeenCalledWith('17');
+ });
});
diff --git a/src/Breadcrumb/index.tsx b/src/Breadcrumb/index.tsx
index 25eed7ff3d..343c922f09 100644
--- a/src/Breadcrumb/index.tsx
+++ b/src/Breadcrumb/index.tsx
@@ -19,7 +19,7 @@ interface BreadcrumbProps {
spacer?: React.ReactElement;
/** allows to add a custom function to be called `onClick` of a breadcrumb link.
* The use case for this is for adding custom analytics to the component. */
- clickHandler?: (event: React.MouseEvent, link: any) => void;
+ clickHandler?: (event: React.MouseEvent, link: any) => void;
/** The `Breadcrumbs` style variant to use. */
variant?: 'light' | 'dark';
/** The `Breadcrumbs` mobile variant view. */
@@ -28,11 +28,13 @@ interface BreadcrumbProps {
* [react-router's Link](https://reactrouter.com/en/main/components/link).
*/
linkAs?: React.ElementType;
+ /** Optional class name(s) to append to the base `