Skip to content

Commit f34f4df

Browse files
feat: support merging base component props, custom props, and pluginProps via slotOptions.mergeProps (#84)
* feat: support merging props with PluginOperations.Modify * fix: provide default content object for widget modification * chore: pr feedback * chore: consolidate examples * chore: remove content prop from LinkExample * chore: pr feedback, updating comment
1 parent bf8705b commit f34f4df

27 files changed

+786
-142
lines changed

README.rst

+2-4
Original file line numberDiff line numberDiff line change
@@ -228,13 +228,11 @@ or its priority. The operation requires the id of the widget that will be modifi
228228
.. code-block::
229229
230230
const modifyWidget = (widget) => {
231-
const newContent = {
231+
widget.content = {
232232
propExampleA: 'University XYZ Sidebar',
233233
propExampleB: SomeOtherIcon,
234234
};
235-
const modifiedWidget = widget;
236-
modifiedWidget.content = newContent;
237-
return modifiedWidget;
235+
return widget;
238236
};
239237
240238
/*

example-plugin-app/src/DefaultIframe.jsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Plugin } from '@openedx/frontend-plugin-framework';
66
function DefaultComponent() {
77
return (
88
<section className="bg-light p-3 h-100">
9-
<h3>Default iFrame Widget</h3>
9+
<h4>Default iFrame Widget</h4>
1010
<p>
1111
This is a component that lives in the example-plugins-app and is provided in this host MFE via iFrame.
1212
</p>
@@ -17,7 +17,7 @@ function DefaultComponent() {
1717
function ErrorFallback(error) {
1818
return (
1919
<div className="text-center">
20-
<p className="h3 text-muted">
20+
<p className="h4 text-muted">
2121
Oops! An error occurred. Please refresh the screen to try again.
2222
</p>
2323
<br />

example-plugin-app/src/PluginIframe.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export default function PluginIframe() {
55
return (
66
<Plugin>
77
<section className="bg-light p-3 h-100">
8-
<h3>Inserted iFrame Plugin</h3>
8+
<h4>Inserted iFrame Plugin</h4>
99
<p>
1010
This is a component that lives in the example-plugins-app and is provided in this host MFE via iFrame plugin.
1111
</p>

example/env.config.jsx

+48-11
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,43 @@ import {
44
IFRAME_PLUGIN,
55
PLUGIN_OPERATIONS,
66
} from '@openedx/frontend-plugin-framework';
7-
import DefaultDirectWidget from './src/components/DefaultDirectWidget';
87
import PluginDirect from './src/components/PluginDirect';
98
import ModularComponent from './src/components/ModularComponent';
109

1110
const modifyWidget = (widget) => {
12-
const newContent = {
11+
widget.content = {
1312
title: 'Modified Modular Plugin',
1413
uniqueText: 'Note that the original text defined in the JS config is replaced by this modified one.',
1514
};
16-
const modifiedWidget = widget;
17-
modifiedWidget.content = newContent;
18-
return modifiedWidget;
15+
return widget;
1916
};
2017

21-
const wrapWidget = ({ component, idx }) => (
22-
<div className="bg-warning" data-testid={`wrapper${idx + 1}`} key={idx}>
23-
<p>This is a wrapper component that is placed around the default content.</p>
18+
const modifyWidgetDefaultContentsUsernamePII = (widget) => {
19+
widget.content = {
20+
'data-custom-attr': 'customValue',
21+
'data-another-custom-attr': '',
22+
className: 'font-weight-bold',
23+
style: { color: 'blue' },
24+
onClick: (e) => { console.log('Username clicked!', 'custom', e); },
25+
};
26+
return widget;
27+
};
28+
29+
const modifyWidgetDefaultContentsLink = (widget) => {
30+
widget.content.href = 'https://openedx.org';
31+
return widget;
32+
};
33+
34+
const wrapWidget = ({ component }) => (
35+
<div className="bg-warning" data-testid="wrapper">
36+
<div className="px-3">
37+
<p className="mb-0">This is a wrapper component that is placed around the default content.</p>
38+
</div>
2439
{component}
25-
<p>With this wrapper, you can add anything before or after a component.</p>
26-
<p>Note in the JS config that an iFrame plugin was Inserted, but a Hide operation was also used to hide it!</p>
40+
<div className="px-3">
41+
<p>With this wrapper, you can add anything before or after a component.</p>
42+
<p className="mb-0">Note in the JS config that an iFrame plugin was Inserted, but a Hide operation was also used to hide it!</p>
43+
</div>
2744
</div>
2845
);
2946

@@ -179,7 +196,27 @@ const config = {
179196
},
180197
},
181198
],
182-
}
199+
},
200+
slot_with_username_pii: {
201+
keepDefault: true,
202+
plugins: [
203+
{
204+
op: PLUGIN_OPERATIONS.Modify,
205+
widgetId: 'default_contents',
206+
fn: modifyWidgetDefaultContentsUsernamePII,
207+
},
208+
],
209+
},
210+
slot_with_hyperlink: {
211+
keepDefault: true,
212+
plugins: [
213+
{
214+
op: PLUGIN_OPERATIONS.Modify,
215+
widgetId: 'default_contents',
216+
fn: modifyWidgetDefaultContentsLink,
217+
},
218+
],
219+
},
183220
},
184221
};
185222

example/package-lock.json

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"author": "edX",
1313
"license": "AAGPL-3.0",
1414
"dependencies": {
15+
"classnames": "^2.5.1",
1516
"core-js": "^3.29.1",
1617
"prop-types": "^15.8.1",
1718
"react": "^17.0.0",

example/src/ExamplePage.jsx

+63-20
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,74 @@
1-
import React from 'react';
1+
import {
2+
Container, Row, Col, Stack,
3+
} from '@openedx/paragon';
24

5+
import PluginSlotWithModifyDefaultContents from './pluginSlots/PluginSlotWithModifyDefaultContents';
36
import PluginSlotWithInsert from './pluginSlots/PluginSlotWithInsert';
47
import PluginSlotWithModifyWrapHide from './pluginSlots/PluginSlotWithModifyWrapHide';
58
import PluginSlotWithModularPlugins from './pluginSlots/PluginSlotWithModularPlugins';
69
import PluginSlotWithoutDefault from './pluginSlots/PluginSlotWithoutDefault';
710

11+
const pluginExamples = [
12+
{
13+
id: 'plugin-operation-insert',
14+
label: 'Plugin Operation: Insert',
15+
Component: PluginSlotWithInsert,
16+
},
17+
{
18+
id: 'plugin-operation-modify-wrap-hide',
19+
label: 'Plugin Operation: Modify, Wrap, and Hide',
20+
Component: PluginSlotWithModifyWrapHide,
21+
},
22+
{
23+
id: 'plugin-operation-modify-default-content',
24+
label: 'Plugin Operation: Modify Default Content',
25+
Component: PluginSlotWithModifyDefaultContents,
26+
},
27+
{
28+
id: 'direct-plugins-modular-components',
29+
label: 'Direct Plugins Using Modular Components',
30+
Component: PluginSlotWithModularPlugins,
31+
},
32+
{
33+
id: 'no-default-content',
34+
label: 'Default Content Set to False',
35+
Component: PluginSlotWithoutDefault,
36+
},
37+
];
38+
839
export default function ExamplePage() {
940
return (
10-
<main className="center m-5">
11-
<h1>Plugins Page</h1>
12-
13-
<p>
14-
This page is here to help test the plugins module. A plugin configuration can be added in
15-
index.jsx and this page will display that plugin.
16-
</p>
17-
<p>
18-
To do this, a plugin MFE must be running on some other port.
19-
To make it a more realistic test, you may also want to edit your
20-
/etc/hosts file (or your system&apos;s equivalent) to provide an alternate domain for
21-
127.0.0.1 at which you can load the plugin.
22-
</p>
23-
<div className="d-flex flex-column">
24-
<PluginSlotWithInsert />
25-
<PluginSlotWithModifyWrapHide />
26-
<PluginSlotWithModularPlugins />
27-
<PluginSlotWithoutDefault />
28-
</div>
41+
<main>
42+
<Container size="lg" className="py-3">
43+
<Row>
44+
<Col>
45+
<h1>Plugins Page</h1>
46+
<p>
47+
This page is here to help test the plugins module. A plugin configuration can be added in
48+
index.jsx and this page will display that plugin.
49+
</p>
50+
<p>
51+
To do this, a plugin MFE must be running on some other port.
52+
To make it a more realistic test, you may also want to edit your
53+
/etc/hosts file (or your system&apos;s equivalent) to provide an alternate domain for
54+
127.0.0.1 at which you can load the plugin.
55+
</p>
56+
<h2>Examples</h2>
57+
<Stack gap={3}>
58+
<ul>
59+
{pluginExamples.map(({ id, label }) => (
60+
<li key={id}>
61+
<a href={`#${id}`}>{label}</a>
62+
</li>
63+
))}
64+
</ul>
65+
<Stack gap={5}>
66+
{pluginExamples.map(({ Component, id, label }) => <Component key={id} id={id} label={label} />)}
67+
</Stack>
68+
</Stack>
69+
</Col>
70+
</Row>
71+
</Container>
2972
</main>
3073
);
3174
}

example/src/components/DefaultDirectWidget.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React from 'react';
33
export default function DefaultDirectWidget() {
44
return (
55
<section className="bg-success p-3 text-light">
6-
<h3>Default Direct Widget</h3>
6+
<h4>Default Direct Widget</h4>
77
<p>
88
This widget is a default component that lives in the example app and is directly inserted via JS configuration.
99
Note that this default widget appears after the Inserted Direct Plugin. This is because this component&apos;s

example/src/components/ModularComponent.jsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import PropTypes from 'prop-types';
44
export default function ModularComponent({ content }) {
55
return (
66
<section className="bg-light p-3">
7-
<h3>{ content.title }</h3>
7+
<h4>{content.title}</h4>
88
<p>
99
This is a modular component that lives in the example app.
1010
</p>
11-
<p>
11+
<p className="mb-0">
1212
<em>{content.uniqueText}</em>
1313
</p>
1414
</section>

example/src/components/PluginDirect.jsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React from 'react';
33
export default function PluginDirect() {
44
return (
55
<section className="bg-warning p-3">
6-
<h3>Inserted Direct Plugin</h3>
6+
<h4>Inserted Direct Plugin</h4>
77
<p>
88
This plugin is a component that lives in the example app and is directly inserted via JS configuration.
99
What makes this unique is that it isn&apos;t part of the default content defined for this slot, but is instead

example/src/pluginSlots/PluginSlotWithInsert.jsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import React from 'react';
22

33
import { PluginSlot } from '@openedx/frontend-plugin-framework';
44

5-
function PluginSlotWithInsert() {
5+
function PluginSlotWithInsert({ id, label }) {
66
return (
7-
<div className="border border-primary mb-2">
8-
<h2 className="pl-3">Plugin Operation: Insert</h2>
7+
<div className="border border-primary">
8+
<h3 id={id} className="pl-3">{label}</h3>
99
<PluginSlot
1010
id="slot_with_insert_operation"
1111
>
1212
<section className="bg-success p-3 text-light">
13-
<h3>Default Content</h3>
13+
<h4>Default Content</h4>
1414
<p>
1515
This widget represents a component that is wrapped by the Plugin Slot.
1616

0 commit comments

Comments
 (0)