Added POC for resizing iframe to fit outer container

refs https://github.com/TryGhost/Team/issues/3295
This commit is contained in:
Simon Backx 2023-06-01 11:56:08 +02:00
parent 990395594b
commit 17e9b803e4
9 changed files with 92 additions and 57 deletions

View File

@ -42,17 +42,21 @@ export default class SignupFormEmbed extends Component {
const options = {
site: siteUrl,
color: this.settings.accentColor
'button-color': this.settings.accentColor
};
for (const [i, label] of this.labels.entries()) {
options[`label-${i + 1}`] = label.name;
}
let style = 'height: 58px';
if (this.style === 'all-in-one') {
options.logo = this.settings.icon;
options.title = this.settings.title;
options.description = this.settings.description;
options['background-color'] = '#f9f9f9';
style = 'height: 60vh; min-height: 400px;';
}
let dataOptionsString = '';
@ -60,7 +64,7 @@ export default class SignupFormEmbed extends Component {
dataOptionsString += ` data-${key}="${escapeHtml(value)}"`;
}
return `<script src="${encodeURI(scriptUrl)}"${dataOptionsString}></script>`;
return `<div style="${escapeHtml(style)}"><script src="${encodeURI(scriptUrl)}"${dataOptionsString}></script></div>`;
}
@task

View File

@ -18,55 +18,64 @@
</p>
<!-- Because we need to use ESM modules during develoment, the src should be different to force reexecution of each script -->
<script
type="module"
src="/src/index.tsx"
data-title="My site name"
data-description="An independent publication about embeddable signup forms."
data-logo="https://user-images.githubusercontent.com/65487235/157884383-1b75feb1-45d8-4430-b636-3f7e06577347.png"
data-background-color="#eeeeee"
data-button-color="#4664dd"
data-site="%VITE_SITE_URL%"
data-label-1="Signup form"
data-label-2="With logo"
></script>
<div style="height: 60vh; min-height: 400px;">
<script
type="module"
src="/src/index.tsx"
data-title="My site name"
data-description="An independent publication about embeddable signup forms."
data-logo="https://user-images.githubusercontent.com/65487235/157884383-1b75feb1-45d8-4430-b636-3f7e06577347.png"
data-background-color="#eeeeee"
data-button-color="#4664dd"
data-site="%VITE_SITE_URL%"
data-label-1="Signup form"
data-label-2="With logo"
></script>
</div>
<hr>
<h1>Without logo</h1>
<script
type="module"
src="/src/index.tsx?withoutlogo"
data-title="Site without logo"
data-description="An independent publication about embeddable signup forms."
data-background-color="#eeeeee"
data-button-color="#4664dd"
data-site="%VITE_SITE_URL%"
data-label-1="Signup form"
data-label-2="Without logo"
></script>
<div style="height: 60vh; min-height: 400px;">
<script
type="module"
src="/src/index.tsx?withoutlogo"
data-title="Site without logo"
data-description="An independent publication about embeddable signup forms."
data-background-color="#eeeeee"
data-button-color="#4664dd"
data-site="%VITE_SITE_URL%"
data-label-1="Signup form"
data-label-2="Without logo"
></script>
</div>
<hr>
<h1>Minimal</h1>
<script
type="module"
src="/src/index.tsx?other"
data-button-color="#ff0095"
data-site="%VITE_SITE_URL%"
data-label-1="Signup form"
data-label-2="Minimal"
></script>
<div style="height: 58px">
<script
type="module"
src="/src/index.tsx?other"
data-button-color="#ff0095"
data-site="%VITE_SITE_URL%"
data-label-1="Signup form"
data-label-2="Minimal"
></script>
</div>
<hr>
<h1>With invalid configuration</h1>
<p>When you submit this one, it will throw an error.</p>
<script
type="module"
src="/src/index.tsx?other2"
data-button-color="#ff0095"
data-site="https://invalid/"
></script>
<div style="height: 58px">
<script
type="module"
src="/src/index.tsx?other2"
data-button-color="#ff0095"
data-site="https://invalid/"
></script>
</div>
</div>
</body>
</html>

View File

@ -1,6 +1,6 @@
import React, {ComponentProps} from 'react';
import pages, {Page, PageName} from './pages';
import {AppContextProvider} from './AppContext';
import {AppContextProvider, AppContextType} from './AppContext';
import {ContentBox} from './components/ContentBox';
import {Frame} from './components/Frame';
import {setupGhostApi} from './utils/api';
@ -29,11 +29,12 @@ const App: React.FC<AppProps> = ({scriptTag}) => {
} as Page);
};
const context = {
const context: AppContextType = {
page,
api,
options,
setPage: _setPage
setPage: _setPage,
scriptTag
};
const PageComponent = pages[page.name];

View File

@ -18,6 +18,7 @@ export type AppContextType = {
setPage: <T extends PageName>(name: T, data: ComponentProps<typeof pages[T]>) => void,
options: SignupFormOptions,
api: GhostApi,
scriptTag: HTMLElement
}
const AppContext = React.createContext<AppContextType>({} as any);

View File

@ -39,9 +39,10 @@ const Preview: React.FC<SignupFormOptions & {
return simulateApiError ? false : true;
}
},
options
options,
scriptTag: document.createElement('div')
}}>
<div style={{width: '100%', height: '100%', padding: '24px', backgroundColor: pageBackgroundColor, color: pageTextColor}}>
<div style={{width: '100%', height: '100%', backgroundColor: pageBackgroundColor, color: pageTextColor}}>
<ContentBox>
<PageComponent {...data} />
</ContentBox>

View File

@ -1,6 +1,7 @@
import IFrame from './IFrame';
import React, {useCallback, useState} from 'react';
import styles from '../styles/iframe.css?inline';
import {useAppContext} from '../AppContext';
type FrameProps = {
children: React.ReactNode
@ -15,9 +16,9 @@ export const Frame: React.FC<FrameProps> = ({children}) => {
height: '0px' // = default height
};
return (
<ResizableFrame style={style} title="signup frame">
<FullHeightFrame style={style} title="signup frame">
{children}
</ResizableFrame>
</FullHeightFrame>
);
};
@ -27,28 +28,46 @@ type ResizableFrameProps = FrameProps & {
};
/**
* This TailwindFrame has the same height as it contents and mimics a shadow DOM component
* This TailwindFrame has the same height as its container
*/
const ResizableFrame: React.FC<ResizableFrameProps> = ({children, style, title}) => {
const FullHeightFrame: React.FC<ResizableFrameProps> = ({children, style, title}) => {
const {scriptTag} = useAppContext();
const [iframeStyle, setIframeStyle] = useState(style);
const onResize = useCallback((iframeRoot: HTMLElement) => {
const onResize = useCallback((element: HTMLElement) => {
setIframeStyle((current) => {
return {
...current,
height: `${iframeRoot.scrollHeight}px`
height: `${element.scrollHeight}px`,
width: `${element.scrollWidth}px`
};
});
}, []);
React.useEffect(() => {
const element = scriptTag.parentElement;
if (!element) {
return;
}
const observer = new ResizeObserver(_ => onResize(element));
observer.observe(element);
return () => {
observer.unobserve(element);
};
}, [scriptTag, onResize]);
return (
<TailwindFrame style={iframeStyle} title={title} onResize={onResize}>
{children}
</TailwindFrame>
<div style={{position: 'absolute'}}>
<TailwindFrame style={iframeStyle} title={title}>
{children}
</TailwindFrame>
</div>
);
};
type TailwindFrameProps = ResizableFrameProps & {
onResize: (el: HTMLElement) => void
onResize?: (el: HTMLElement) => void
};
/**

View File

@ -10,7 +10,7 @@ export default class IFrame extends Component<any> {
iframeHead: any;
iframeRoot: any;
constructor(props: {onResize: (el: HTMLElement) => void, children: any}) {
constructor(props: {onResize?: (el: HTMLElement) => void, children: any}) {
super(props);
this.setNode = this.setNode.bind(this);
this.node = null;

View File

@ -16,7 +16,7 @@ export const FormView: React.FC<FormProps & {
return (
<div
className='flex h-[52vmax] min-h-[320px] flex-col items-center justify-center p-6 md:p-8'
className='flex h-[100vh] flex-col items-center justify-center p-6 md:p-8'
data-testid="wrapper"
style={{backgroundColor, color: backgroundColor && textColorForBackgroundColor(backgroundColor)}}
>

View File

@ -15,7 +15,7 @@ export const SuccessView: React.FC<{
}
return (
<div
className='flex h-[52vmax] min-h-[320px] flex-col items-center justify-center bg-grey-200 p-6 md:p-8'
className='flex h-[100vh] flex-col items-center justify-center bg-grey-200 p-6 md:p-8'
data-testid="success-page"
style={{backgroundColor, color: backgroundColor && textColorForBackgroundColor(backgroundColor)}}
>