Skip to content

Commit caef79f

Browse files
authored
Merge pull request #28 from renatasva/add-attributes-option
2 parents 7cba832 + e3cc21b commit caef79f

File tree

4 files changed

+86
-6
lines changed

4 files changed

+86
-6
lines changed

README.md

+13-3
Original file line numberDiff line numberDiff line change
@@ -111,13 +111,23 @@ Normally in `ComponentA`/`ComponentB` examples like the above, switching from `C
111111

112112
How does it work under the hood?
113113

114-
### `portals.createHtmlPortalNode`
114+
### `portals.createHtmlPortalNode([options])`
115115

116116
This creates a detached DOM node, with a little extra functionality attached to allow transmitting props later on.
117117

118-
This node will contain your portal contents later, within a `<div>`, and will eventually be attached in the target location. Its plain DOM node is available at `.element`, so you can mutate that to set any required props (e.g. `className`) with the standard DOM APIs.
118+
This node will contain your portal contents later, within a `<div>`, and will eventually be attached in the target location.
119119

120-
### `portals.createSvgPortalNode`
120+
An optional options object parameter can be passed to configure the node. The only supported option is `attributes`: this can be used to set the HTML attributes (style, class, etc.) of the intermediary, like so:
121+
122+
```javascript
123+
const portalNode = portals.createHtmlPortalNode({
124+
attributes: { id: "div-1", style: "background-color: #aaf; width: 100px;" }
125+
});
126+
```
127+
128+
The div's DOM node is also available at `.element`, so you can mutate that directly with the standard DOM APIs if preferred.
129+
130+
### `portals.createSvgPortalNode([options])`
121131

122132
This creates a detached SVG DOM node. It works identically to the node from `createHtmlPortalNode`, except it will work with SVG elements. Content is placed within a `<g>` instead of a `<div>`.
123133

src/index.tsx

+16-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ const ELEMENT_TYPE_SVG = 'svg';
77

88
type ANY_ELEMENT_TYPE = typeof ELEMENT_TYPE_HTML | typeof ELEMENT_TYPE_SVG;
99

10+
type Options = {
11+
attributes: { [key: string]: string };
12+
};
13+
1014
// ReactDOM can handle several different namespaces, but they're not exported publicly
1115
// https://github.com/facebook/react/blob/b87aabdfe1b7461e7331abb3601d9e6bb27544bc/packages/react-dom/src/shared/DOMNamespaces.js#L8-L10
1216
const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
@@ -50,7 +54,10 @@ const validateElementType = (domElement: Element, elementType: ANY_ELEMENT_TYPE)
5054
};
5155

5256
// This is the internal implementation: the public entry points set elementType to an appropriate value
53-
const createPortalNode = <C extends Component<any>>(elementType: ANY_ELEMENT_TYPE): AnyPortalNode<C> => {
57+
const createPortalNode = <C extends Component<any>>(
58+
elementType: ANY_ELEMENT_TYPE,
59+
options?: Options
60+
): AnyPortalNode<C> => {
5461
let initialProps = {} as ComponentProps<C>;
5562

5663
let parent: Node | undefined;
@@ -65,6 +72,12 @@ const createPortalNode = <C extends Component<any>>(elementType: ANY_ELEMENT_TYP
6572
throw new Error(`Invalid element type "${elementType}" for createPortalNode: must be "html" or "svg".`);
6673
}
6774

75+
if (options && typeof options === "object") {
76+
for (const [key, value] of Object.entries(options.attributes)) {
77+
element.setAttribute(key, value);
78+
}
79+
}
80+
6881
const portalNode: AnyPortalNode<C> = {
6982
element,
7083
elementType,
@@ -223,9 +236,9 @@ class OutPortal<C extends Component<any>> extends React.PureComponent<OutPortalP
223236
}
224237

225238
const createHtmlPortalNode = createPortalNode.bind(null, ELEMENT_TYPE_HTML) as
226-
<C extends Component<any> = Component<any>>() => HtmlPortalNode<C>;
239+
<C extends Component<any> = Component<any>>(options?: Options) => HtmlPortalNode<C>;
227240
const createSvgPortalNode = createPortalNode.bind(null, ELEMENT_TYPE_SVG) as
228-
<C extends Component<any> = Component<any>>() => SvgPortalNode<C>;
241+
<C extends Component<any> = Component<any>>(options?: Options) => SvgPortalNode<C>;
229242

230243
export {
231244
createHtmlPortalNode,

stories/html.stories.js

+29
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,35 @@ storiesOf('Portals', module)
260260

261261
return <ChangeLayoutWithoutUnmounting />;
262262
})
263+
.add('can pass attributes option to createHtmlPortalNode', () => {
264+
return React.createElement(() => {
265+
const [hasAttrOption, setHasAttrOption] = React.useState(false);
266+
267+
const portalNode = createHtmlPortalNode( hasAttrOption ? {
268+
attributes: { id: "div-id-1", style: "background-color: #aaf; width: 204px;" }
269+
} : null);
270+
271+
return <div>
272+
<button onClick={() => setHasAttrOption(!hasAttrOption)}>
273+
Click to pass attributes option to the intermediary div
274+
</button>
275+
276+
<hr/>
277+
278+
<InPortal node={portalNode}>
279+
<div style={{width: '200px', height: '50px', border: '2px solid purple'}} />
280+
</InPortal>
281+
282+
<OutPortal node={portalNode} />
283+
284+
<br/>
285+
<br/>
286+
<br/>
287+
288+
<text>{!hasAttrOption ? `const portalNode = createHtmlPortalNode();` : `const portalNode = createHtmlPortalNode({ attributes: { id: "div-id-1", style: "background-color: #aaf; width: 204px;" } });`}</text>
289+
</div>
290+
});
291+
})
263292
.add('Example from README', () => {
264293
const MyExpensiveComponent = () => 'expensive!';
265294

stories/svg.stories.js

+28
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,32 @@ storiesOf('SVG Portals', module)
9292
</svg>
9393
</div>
9494
})
95+
})
96+
.add('can pass attributes option to createSvgPortalNode', () => {
97+
return React.createElement(() => {
98+
const [hasAttrOption, setHasAttrOption] = React.useState(false);
99+
100+
const portalNode = createSvgPortalNode(hasAttrOption ? {attributes: { stroke: 'blue' }} : null);
101+
return <div>
102+
<button onClick={() => setHasAttrOption(!hasAttrOption)}>
103+
Click to pass attributes option to the intermediary svg
104+
</button>
105+
106+
<hr/>
107+
108+
<svg>
109+
<InPortal node={portalNode}>
110+
<circle cx="50" cy="50" r="40" fill="lightblue" />
111+
</InPortal>
112+
113+
<svg x={30} y={10}>
114+
<OutPortal node={portalNode} />
115+
</svg>
116+
</svg>
117+
118+
<br/>
119+
120+
<text>{!hasAttrOption ? `const portalNode = createSvgPortalNode();` : `const portalNode = createSvgPortalNode({ attributes: { stroke: "blue" } });`}</text>
121+
</div>
122+
});
95123
});

0 commit comments

Comments
 (0)