Skip to content

Commit 7f20891

Browse files
Merge pull request #4079 from openedx/release-23.x
sync: release-23.x to next
2 parents 0a832f4 + 9e30e8c commit 7f20891

File tree

7 files changed

+80
-27
lines changed

7 files changed

+80
-27
lines changed

README.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -514,10 +514,34 @@ Paragon components can have different behavior in the MFE environment. `example`
514514

515515
Steps to install the `example` app.
516516

517+
To set up the example app with a local Tutor installation, you'll need to configure a Tutor plugin to handle CORS settings
518+
519+
Prerequisites.
520+
521+
Before running the example app, ensure you have a local Tutor installation configured.
522+
1. Create a new file (i.e. `paragon-example-app.py`) in your tutor plugins folder (`tutor plugins printroot` to get the path).
523+
2. Add the following content to the paragon-example-app.py file:
524+
```
525+
from tutor import hooks
526+
527+
hooks.Filters.ENV_PATCHES.add_item(
528+
(
529+
"openedx-lms-development-settings",
530+
"""
531+
CORS_ORIGIN_WHITELIST.append("http://localhost:8080")
532+
LOGIN_REDIRECT_WHITELIST.append("http://localhost:8080")
533+
CSRF_TRUSTED_ORIGINS.append("http://localhost:8080")
534+
"""
535+
)
536+
)
537+
```
538+
3. Enable the plugin: `tutor plugins enable paragon-example-app`
539+
4. Apply configuration changes: `tutor config save` && `tutor dev restart`
540+
541+
Running the Example App:
517542
1. `npm install` to install dependencies.
518-
2. Launch any devstack. It is required for MFE to login into the system and set up configs.
519-
3. `npm run start-example-mfe` to start the app.
520-
4. Go to the `example/src/MyComponent.jsx` and use Paragon components inside the MFE environment.
543+
2. `npm run example:start` to start the app.
544+
3. Go to the `example/src/MyComponent.jsx` and use Paragon components inside the MFE environment.
521545

522546
## Semantic Release
523547

example/.env.development

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
NODE_ENV='development'
2-
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
3-
LMS_BASE_URL='http://localhost:18000'
4-
ACCESS_TOKEN_COOKIE_NAME='edx-jwt-cookie-header-payload'
1+
NODE_ENV=development
2+
REFRESH_ACCESS_TOKEN_ENDPOINT=http://local.openedx.io:8000/login_refresh
3+
LMS_BASE_URL=http://local.openedx.io:8000
4+
ACCESS_TOKEN_COOKIE_NAME=edx-jwt-cookie-header-payload
5+
BASE_URL=http://local.openedx.io:8080
6+
LOGIN_URL=http://local.openedx.io:8000/login
7+
LOGOUT_URL=http://local.openedx.io:8000/logout
8+
CSRF_TOKEN_API_PATH=/csrf/api/v1/token

example/public/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<!doctype html>
22
<html lang="en-us">
33
<head>
4-
<title>Example</title>
4+
<title>Paragon Example</title>
55
<meta charset="utf-8">
66
<meta name="viewport" content="width=device-width, initial-scale=1.0">
77
</head>

example/src/index.jsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,30 @@
1-
import React from 'react';
2-
import ReactDOM from 'react-dom';
1+
import { createRoot } from 'react-dom/client';
32
import {
43
AppProvider,
54
ErrorPage,
6-
PageRoute,
5+
PageWrap,
76
} from '@edx/frontend-platform/react';
87
import { APP_INIT_ERROR, APP_READY, initialize } from '@edx/frontend-platform';
98
import { subscribe } from '@edx/frontend-platform/pubSub';
109
import MyComponent from './MyComponent';
1110

1211
import './index.scss';
1312

13+
const container = document.getElementById('root');
14+
const root = createRoot(container);
15+
1416
subscribe(APP_READY, () => {
15-
ReactDOM.render(
17+
root.render(
1618
<AppProvider>
17-
<PageRoute
18-
exact
19-
path="/"
20-
component={MyComponent}
21-
/>
19+
<PageWrap>
20+
<MyComponent />
21+
</PageWrap>
2222
</AppProvider>,
23-
document.getElementById('root'),
2423
);
2524
});
2625

2726
subscribe(APP_INIT_ERROR, (error) => {
28-
ReactDOM.render(<ErrorPage message={error.message} />, document.getElementById('root'));
27+
root.render(<ErrorPage message={error.message} />);
2928
});
3029

3130
initialize({

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,29 @@ describe('<Breadcrumb />', () => {
9595
expect(links.getAttribute('target')).toBe('_blank');
9696
expect(links.getAttribute('href')).toBe('/link-1');
9797
});
98+
99+
it('renders with a custom CSS class', () => {
100+
render(<Breadcrumb {...baseProps} ariaLabel="Breadcrumbs" className="foobar" />);
101+
const nav = screen.getByLabelText('Breadcrumbs');
102+
expect(nav.classList).toContain('foobar');
103+
expect(nav.classList).toContain('pgn__breadcrumb');
104+
expect(nav.classList).toContain('pgn__breadcrumb-light');
105+
});
106+
107+
it('can access data-xxxxx attributes on the links in clickHandler', async () => {
108+
const user = userEvent.setup();
109+
const clickHandler = jest.fn();
110+
render(
111+
<Breadcrumb
112+
ariaLabel="Location"
113+
links={[
114+
{ label: 'Link1', href: '/link1', 'data-parent-index': 17 },
115+
]}
116+
clickHandler={({ currentTarget }) => clickHandler(currentTarget.dataset.parentIndex)}
117+
/>,
118+
);
119+
const link = screen.getByRole('link', { name: 'Link1' });
120+
await user.click(link);
121+
expect(clickHandler).toHaveBeenCalledWith('17');
122+
});
98123
});

src/Breadcrumb/index.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ interface BreadcrumbProps {
1919
spacer?: React.ReactElement;
2020
/** allows to add a custom function to be called `onClick` of a breadcrumb link.
2121
* The use case for this is for adding custom analytics to the component. */
22-
clickHandler?: (event: React.MouseEvent, link: any) => void;
22+
clickHandler?: (event: React.MouseEvent<HTMLAnchorElement>, link: any) => void;
2323
/** The `Breadcrumbs` style variant to use. */
2424
variant?: 'light' | 'dark';
2525
/** The `Breadcrumbs` mobile variant view. */
@@ -28,20 +28,21 @@ interface BreadcrumbProps {
2828
* [react-router's Link](https://reactrouter.com/en/main/components/link).
2929
*/
3030
linkAs?: React.ElementType;
31+
/** Optional class name(s) to append to the base `<nav>` element. */
32+
className?: string;
3133
}
3234

3335
function Breadcrumb({
34-
links, activeLabel, spacer, clickHandler,
35-
variant = 'light', isMobile = false, ariaLabel = 'breadcrumb', linkAs = 'a', ...props
36+
links, activeLabel, spacer, clickHandler, className,
37+
variant = 'light', isMobile = false, ariaLabel = 'breadcrumb', linkAs = 'a',
3638
}: BreadcrumbProps) {
3739
const linkCount = links.length;
3840
const displayLinks = isMobile ? [links[linkCount - 1]] : links;
3941

4042
return (
4143
<nav
4244
aria-label={ariaLabel}
43-
className={classNames('pgn__breadcrumb', `pgn__breadcrumb-${variant}`)}
44-
{...props}
45+
className={classNames('pgn__breadcrumb', `pgn__breadcrumb-${variant}`, className)}
4546
>
4647
<ol className={classNames('list-inline', { 'is-mobile': isMobile })}>
4748
{displayLinks.map((link, i) => (

0 commit comments

Comments
 (0)