-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: support merging base component props, custom props, and pluginProps
via slotOptions.mergeProps
#84
feat: support merging base component props, custom props, and pluginProps
via slotOptions.mergeProps
#84
Changes from 2 commits
aff65a1
d115d44
be17231
0e80e82
275d3a4
751831b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
---|---|---|
@@ -1,31 +1,80 @@ | ||
import React from 'react'; | ||
import { | ||
Container, Row, Col, Stack, | ||
} from '@openedx/paragon'; | ||
|
||
import PluginSlotWithModifyDefaultContentsLink from './pluginSlots/PluginSlotWithModifyDefaultContentsLink'; | ||
import PluginSlotWithModifyDefaultContents from './pluginSlots/PluginSlotWithModifyDefaultContents'; | ||
import PluginSlotWithInsert from './pluginSlots/PluginSlotWithInsert'; | ||
import PluginSlotWithModifyWrapHide from './pluginSlots/PluginSlotWithModifyWrapHide'; | ||
import PluginSlotWithModularPlugins from './pluginSlots/PluginSlotWithModularPlugins'; | ||
import PluginSlotWithoutDefault from './pluginSlots/PluginSlotWithoutDefault'; | ||
|
||
const pluginExamples = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [inform/context] Refactored |
||
{ | ||
id: 'plugin-operation-insert', | ||
label: 'Plugin Operation: Insert', | ||
Component: PluginSlotWithInsert, | ||
}, | ||
{ | ||
id: 'plugin-operation-modify-wrap-hide', | ||
label: 'Plugin Operation: Modify, Wrap, and Hide', | ||
Component: PluginSlotWithModifyWrapHide, | ||
}, | ||
{ | ||
id: 'plugin-operation-modify-default-content', | ||
label: 'Plugin Operation: Modify Default Content', | ||
Component: PluginSlotWithModifyDefaultContents, | ||
}, | ||
{ | ||
id: 'plugin-operation-modify-default-content-link', | ||
label: 'Plugin Operation: Modify Default Content (Link)', | ||
Component: PluginSlotWithModifyDefaultContentsLink, | ||
}, | ||
{ | ||
id: 'direct-plugins-modular-components', | ||
label: 'Direct Plugins Using Modular Components', | ||
Component: PluginSlotWithModularPlugins, | ||
}, | ||
{ | ||
id: 'no-default-content', | ||
label: 'Default Content Set to False', | ||
Component: PluginSlotWithoutDefault, | ||
}, | ||
]; | ||
|
||
export default function ExamplePage() { | ||
return ( | ||
<main className="center m-5"> | ||
<h1>Plugins Page</h1> | ||
|
||
<p> | ||
This page is here to help test the plugins module. A plugin configuration can be added in | ||
index.jsx and this page will display that plugin. | ||
</p> | ||
<p> | ||
To do this, a plugin MFE must be running on some other port. | ||
To make it a more realistic test, you may also want to edit your | ||
/etc/hosts file (or your system's equivalent) to provide an alternate domain for | ||
127.0.0.1 at which you can load the plugin. | ||
</p> | ||
<div className="d-flex flex-column"> | ||
<PluginSlotWithInsert /> | ||
<PluginSlotWithModifyWrapHide /> | ||
<PluginSlotWithModularPlugins /> | ||
<PluginSlotWithoutDefault /> | ||
</div> | ||
<main> | ||
<Container size="lg" className="py-3"> | ||
<Row> | ||
<Col> | ||
<h1>Plugins Page</h1> | ||
<p> | ||
This page is here to help test the plugins module. A plugin configuration can be added in | ||
index.jsx and this page will display that plugin. | ||
</p> | ||
<p> | ||
To do this, a plugin MFE must be running on some other port. | ||
To make it a more realistic test, you may also want to edit your | ||
/etc/hosts file (or your system's equivalent) to provide an alternate domain for | ||
127.0.0.1 at which you can load the plugin. | ||
</p> | ||
<h2>Examples</h2> | ||
<Stack gap={3}> | ||
<ul> | ||
{pluginExamples.map(({ id, label }) => ( | ||
<li key={id}> | ||
<a href={`#${id}`}>{label}</a> | ||
</li> | ||
))} | ||
</ul> | ||
Comment on lines
+58
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [inform/context] Includes a small list of skip links to navigate to specific example(s), without necessarily needing to scroll to find a specific example. |
||
<Stack gap={5}> | ||
{pluginExamples.map(({ Component, id, label }) => <Component key={id} id={id} label={label} />)} | ||
</Stack> | ||
</Stack> | ||
</Col> | ||
</Row> | ||
</Container> | ||
</main> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import React from 'react'; | ||
|
||
import { PluginSlot } from '@openedx/frontend-plugin-framework'; | ||
import classNames from 'classnames'; | ||
|
||
|
||
// Example component used as the default childen within a PluginSlot | ||
const Username = ({ className, onClick, ...rest }) => { | ||
const authenticatedUser = { username: 'testuser' }; | ||
const { username } = authenticatedUser; | ||
return ( | ||
<span | ||
className={classNames('default-classname', className)} | ||
onClick={(e) => { | ||
console.log('Username clicked!', 'default', e); | ||
onClick?.(e); | ||
}} | ||
{...rest} | ||
> | ||
{username} | ||
</span> | ||
); | ||
}; | ||
|
||
const UsernameWithPluginContent = ({ className, onClick, content = {} }) => { | ||
const { | ||
className: classNameFromPluginContent, | ||
onClick: onClickFromPluginContent, | ||
...contentRest | ||
} = content; | ||
const updatedProps = { | ||
className: classNames(className, classNameFromPluginContent), | ||
onClick: (e) => { | ||
onClick?.(e); | ||
onClickFromPluginContent?.(e); | ||
}, | ||
}; | ||
return <Username {...updatedProps} {...contentRest} />; | ||
}; | ||
|
||
function PluginSlotWithModifyDefaultContents({ id, label }) { | ||
return ( | ||
<div className="border border-primary px-3"> | ||
<h3 id={id}>{label}</h3> | ||
<p> | ||
The following <code>PluginSlot</code> examples demonstrate the <code>PLUGIN_OPERATIONS.Modify</code> operation, when | ||
the <code>widgetId</code> is <code>default_contents</code>. Any configured, custom plugin <code>content</code> is | ||
merged with any existing props passed to the component(s) represented by <code>default_contents</code>. | ||
</p> | ||
<ul> | ||
<li>Custom <code>className</code> overrides are concatenated with the <code>className</code> prop passed to the <code>default_contents</code> component(s), if any.</li> | ||
<li>Custom <code>style</code> overrides are shallow merged with the <code>style</code> prop passed to the <code>default_contents</code> component(s), if any.</li> | ||
<li>Custom event handlers (e.g., <code>onClick</code>) are executed in sequence, after any event handlers passed to the <code>default_contents</code> component(s), if any.</li> | ||
</ul> | ||
<PluginSlot | ||
id="slot_with_username_pii" | ||
as="div" | ||
// Default slotOptions | ||
slotOptions={{ | ||
mergeProps: false, | ||
}} | ||
adamstankiewicz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
> | ||
<UsernameWithPluginContent | ||
className="tetsing" | ||
onClick={(e) => { | ||
console.log('Username clicked!', 'prop', e); | ||
}} | ||
/> | ||
</PluginSlot> | ||
<PluginSlot | ||
id="slot_with_username_pii" | ||
as="div" | ||
slotOptions={{ | ||
mergeProps: true, | ||
}} | ||
> | ||
<Username | ||
className="d-block abc123" | ||
onClick={(e) => { | ||
console.log('Username clicked!', 'prop', e); | ||
}} | ||
/> | ||
{/* <Username className="ghi789" /> // Note: uncomment to see example of incorrectly using multiple `children` elements within `PluginSlot` */} | ||
</PluginSlot> | ||
<PluginSlot | ||
id="slot_with_username_pii" | ||
as="div" | ||
pluginProps={{ | ||
className: 'bg-accent-b', | ||
onClick: (e) => { console.log('Username clicked!', 'pluginProps', e); }, | ||
}} | ||
slotOptions={{ | ||
mergeProps: true, | ||
}} | ||
> | ||
<Username | ||
className="ghi789" | ||
onClick={(e) => { | ||
console.log('Username clicked!', 'prop', e); | ||
}} | ||
/> | ||
</PluginSlot> | ||
</div> | ||
); | ||
} | ||
|
||
export default PluginSlotWithModifyDefaultContents; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[inform/context] Passing the
key
here does not resolve the React console warning; Thekey
needs to be applied to the parentwrapWidget
function as opposed to a node withinwrapWidget
, given its acting as a component (e.g.,wrapWidget
is the display name of the rendered component).