Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 8 additions & 4 deletions example/.env.development
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion example/public/index.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!doctype html>
<html lang="en-us">
<head>
<title>Example</title>
<title>Paragon Example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
Expand Down
21 changes: 10 additions & 11 deletions example/src/index.jsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
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';
import MyComponent from './MyComponent';

import './index.scss';

const container = document.getElementById('root');
const root = createRoot(container);

subscribe(APP_READY, () => {
ReactDOM.render(
root.render(
<AppProvider>
<PageRoute
exact
path="/"
component={MyComponent}
/>
<PageWrap>
<MyComponent />
</PageWrap>
</AppProvider>,
document.getElementById('root'),
);
});

subscribe(APP_INIT_ERROR, (error) => {
ReactDOM.render(<ErrorPage message={error.message} />, document.getElementById('root'));
root.render(<ErrorPage message={error.message} />);
});

initialize({
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,29 @@ describe('<Breadcrumb />', () => {
expect(links.getAttribute('target')).toBe('_blank');
expect(links.getAttribute('href')).toBe('/link-1');
});

it('renders with a custom CSS class', () => {
render(<Breadcrumb {...baseProps} ariaLabel="Breadcrumbs" className="foobar" />);
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(
<Breadcrumb
ariaLabel="Location"
links={[
{ label: 'Link1', href: '/link1', 'data-parent-index': 17 },
]}
clickHandler={({ currentTarget }) => clickHandler(currentTarget.dataset.parentIndex)}
/>,
);
const link = screen.getByRole('link', { name: 'Link1' });
await user.click(link);
expect(clickHandler).toHaveBeenCalledWith('17');
});
});
11 changes: 6 additions & 5 deletions src/Breadcrumb/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLAnchorElement>, link: any) => void;
/** The `Breadcrumbs` style variant to use. */
variant?: 'light' | 'dark';
/** The `Breadcrumbs` mobile variant view. */
Expand All @@ -28,20 +28,21 @@ 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 `<nav>` element. */
className?: string;
}

function Breadcrumb({
links, activeLabel, spacer, clickHandler,
variant = 'light', isMobile = false, ariaLabel = 'breadcrumb', linkAs = 'a', ...props
links, activeLabel, spacer, clickHandler, className,
variant = 'light', isMobile = false, ariaLabel = 'breadcrumb', linkAs = 'a',
}: BreadcrumbProps) {
const linkCount = links.length;
const displayLinks = isMobile ? [links[linkCount - 1]] : links;

return (
<nav
aria-label={ariaLabel}
className={classNames('pgn__breadcrumb', `pgn__breadcrumb-${variant}`)}
{...props}
className={classNames('pgn__breadcrumb', `pgn__breadcrumb-${variant}`, className)}
>
<ol className={classNames('list-inline', { 'is-mobile': isMobile })}>
{displayLinks.map((link, i) => (
Expand Down
Loading