-
Notifications
You must be signed in to change notification settings - Fork 5
Add <HtmlPanel>
component
#4
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
base: master
Are you sure you want to change the base?
Changes from all commits
ffed3f0
914719e
7cfa463
5253bb7
2cde9eb
cbdf35b
faf5bdc
ac6d3ff
7367fd3
e66cdb9
359bb0f
f30cacf
045d7af
b74d9d3
92231d7
6217f1d
9c1cf8b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
|
||
<!-- | ||
Put your app's CSS here. For instance, if your bundler generates a CSS | ||
file from all component styles, load it here as well and your components | ||
will be styled correctly inside the HTML Panel | ||
--> | ||
<link rel="stylesheet" href="./your-styles.css" /> | ||
|
||
<style> | ||
html, | ||
body { | ||
margin: 0; | ||
padding: 0; | ||
} | ||
body { | ||
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. Maybe let's just give 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. Not sure what you mean here; the buttons already have |
||
background-color: lightblue; | ||
} | ||
button { | ||
display: block; | ||
width: 10rem; | ||
margin: 0.6rem auto; | ||
} | ||
</style> | ||
</head> | ||
<body></body> | ||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
import { useContext, useEffect, useState } from "react"; | ||
import { createPortal } from "react-dom"; | ||
import Talk from "talkjs"; | ||
import { BoxContext } from "./MountedBox"; | ||
|
||
type HtmlPanelProps = { | ||
/** | ||
* The URL you want to load inside the HTML panel. The URL can be absolute or | ||
* relative. We recommend using same origin pages to have better control of | ||
* the page. Learn more about HTML Panels and same origin pages {@link https://talkjs.com/docs/Features/Customizations/HTML_Panels/ | here}. | ||
*/ | ||
url: string; | ||
|
||
/** The panel height in pixels. Defaults to `100px`. */ | ||
height?: number; | ||
|
||
/** Sets the visibility of the panel. Defaults to `true`. */ | ||
show?: boolean; | ||
|
||
/** If given, the panel will only show up for the conversation that has an `id` matching the one given. */ | ||
conversationId?: string; | ||
|
||
/** The content that gets rendered inside the `<body>` of the panel. */ | ||
children?: React.ReactNode; | ||
}; | ||
|
||
export function HtmlPanel({ | ||
url, | ||
height = 100, | ||
show = true, | ||
conversationId, | ||
children, | ||
}: HtmlPanelProps) { | ||
const [panel, setPanel] = useState<undefined | Talk.HtmlPanel>(undefined); | ||
const box = useContext(BoxContext); | ||
|
||
const normalizedPanelUrl = new URL(url, document.baseURI); | ||
const baseUrl = new URL(document.baseURI); | ||
const isCrossOrigin = normalizedPanelUrl.origin !== baseUrl.origin; | ||
|
||
useEffect(() => { | ||
async function run() { | ||
if (!box || panel) return Promise.resolve(panel); | ||
|
||
const newPanel = await box.createHtmlPanel({ | ||
url, | ||
conversation: conversationId, | ||
height, | ||
// If the frame is cross-origin, we can't render children into it anyway | ||
// so we show the panel straight away. If we can render, we hide it | ||
// first and wait for the DOMContentLoaded event to fire before showing | ||
// the panel to avoid a flash of content that's missing the React | ||
// portal. | ||
show: isCrossOrigin, | ||
}); | ||
|
||
if (!isCrossOrigin) { | ||
// This promise will never resolve if the panel isn't on the same origin. | ||
// We skip the `await` if that's the case. | ||
await newPanel.DOMContentLoadedPromise; | ||
if (show) { | ||
newPanel.show(); | ||
} | ||
} | ||
|
||
setPanel(newPanel); | ||
return newPanel; | ||
} | ||
|
||
const panelPromise = run(); | ||
|
||
return () => { | ||
panelPromise.then((panel) => { | ||
panel?.destroy().then(() => { | ||
setPanel(undefined); | ||
}); | ||
}); | ||
}; | ||
// We intentionally exclude `height` and `show` from the dependency array so | ||
// that we update them later via methods instead of by re-creating the | ||
// entire panel from scratch each time. | ||
// | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, [url, box, conversationId]); | ||
|
||
useEffect(() => { | ||
panel?.setHeight(height); | ||
}, [panel, height]); | ||
|
||
useEffect(() => { | ||
if (show) { | ||
panel?.show(); | ||
} else { | ||
panel?.hide(); | ||
} | ||
}, [panel, show]); | ||
|
||
const shouldRender = !isCrossOrigin && panel && children; | ||
|
||
return ( | ||
<>{shouldRender && createPortal(children, panel.window.document.body)}</> | ||
); | ||
} |
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.
How about we prepend something here like