diff --git a/.changeset/real-years-watch.md b/.changeset/real-years-watch.md new file mode 100644 index 00000000..39e3528b --- /dev/null +++ b/.changeset/real-years-watch.md @@ -0,0 +1,7 @@ +--- +'@flatfile/react': minor +--- + +Adds new useFlatfile({ onClose }) close event handler on useFlatfile hook +Restores missing stylesheet for legacy deprecated useSpace and usePortal flows +Flatfile modal now fills entire screen by default, style overrides may need to be adjusted diff --git a/apps/react/app/App.tsx b/apps/react/app/App.tsx index 821417d0..f1ec1bfb 100644 --- a/apps/react/app/App.tsx +++ b/apps/react/app/App.tsx @@ -16,7 +16,10 @@ import { useEffect, useState } from 'react' import styles from './page.module.css' const App = () => { - const { open, openPortal, closePortal } = useFlatfile() + function logClosed() { + console.log('Flatfile Portal closed') + } + const { open, openPortal, closePortal } = useFlatfile({ onClose: logClosed }) const [label, setLabel] = useState('Rock') const toggleOpen = () => { open ? closePortal({ reset: false }) : openPortal() diff --git a/apps/react/app/OldApp.tsx b/apps/react/app/OldApp.tsx index 29e34d9e..ca642af0 100644 --- a/apps/react/app/OldApp.tsx +++ b/apps/react/app/OldApp.tsx @@ -1,7 +1,7 @@ 'use client' import { sheet } from '@/utils/sheet' import { InitSpace, initializeFlatfile, usePortal } from '@flatfile/react' -import React, { Dispatch, SetStateAction, useState } from 'react' +import { Dispatch, SetStateAction, useState } from 'react' import { config } from './config' import { listener } from './listener' import styles from './page.module.css' @@ -85,33 +85,43 @@ function App() { return (
-
- -
-
- -
-
- +
+
+ +
+
+ +
+
+ +
{showSpace && (
diff --git a/apps/react/app/globals.css b/apps/react/app/globals.css index 0a2ce99d..2f62d7c9 100644 --- a/apps/react/app/globals.css +++ b/apps/react/app/globals.css @@ -110,7 +110,13 @@ a { } } +.flatfile_iframe-wrapper, +.flatfile_initIframe-wrapper { + top: 60px !important; + height: calc(100dvh - 60px) !important; +} -.flatfile_iframe-wrapper { - top: 100px !important; -} \ No newline at end of file +.flatfile_iFrameContainer, +.flatfile_initIFrameContainer { + height: auto !important; +} diff --git a/apps/react/app/page.tsx b/apps/react/app/page.tsx index f80d3d5e..5d7ea21c 100644 --- a/apps/react/app/page.tsx +++ b/apps/react/app/page.tsx @@ -6,7 +6,9 @@ import { FlatfileProvider } from '@flatfile/react' export default function Home() { const PUBLISHABLE_KEY = process.env.NEXT_PUBLIC_FLATFILE_PUBLISHABLE_KEY - if (!PUBLISHABLE_KEY) return <>No Publishable Key Available + if (!PUBLISHABLE_KEY) { + return <>No Publishable Key Available + } return ( void + setOnClose: (onClose: () => (undefined | (() => void))) => void, setOpen: (open: boolean) => void space?: CreateNewSpace | ReUseSpace sessionSpace?: any @@ -59,6 +61,8 @@ export const FlatfileContext = createContext({ environmentId: undefined, apiUrl: '', open: true, + onClose: undefined, + setOnClose: () => {}, setOpen: () => {}, space: undefined, sessionSpace: undefined, diff --git a/packages/react/src/components/FlatfileProvider.tsx b/packages/react/src/components/FlatfileProvider.tsx index 7aa68248..52b71b2d 100644 --- a/packages/react/src/components/FlatfileProvider.tsx +++ b/packages/react/src/components/FlatfileProvider.tsx @@ -17,9 +17,15 @@ import { convertDatesToISO } from '../utils/convertDatesToISO' import { createSpaceInternal } from '../utils/createSpaceInternal' import { getSpace } from '../utils/getSpace' import { EmbeddedIFrameWrapper } from './EmbeddedIFrameWrapper' -import FlatfileContext, { DEFAULT_CREATE_SPACE } from './FlatfileContext' +import FlatfileContext, { + DEFAULT_CREATE_SPACE, + FlatfileContextType, +} from './FlatfileContext' -import { attachStyleSheet } from '../utils/attachStyleSheet' +import { + attachStyleSheet, + useAttachStyleSheet, +} from '../utils/attachStyleSheet' const configDefaults: IFrameTypes = { preload: true, @@ -41,6 +47,7 @@ export const FlatfileProvider: React.FC = ({ apiUrl = 'https://platform.flatfile.com/api', config, }) => { + useAttachStyleSheet(config?.styleSheetOptions) const [internalAccessToken, setInternalAccessToken] = useState< string | undefined | null >(accessToken) @@ -56,6 +63,8 @@ export const FlatfileProvider: React.FC = ({ space: Flatfile.SpaceConfig & { id?: string } }>(DEFAULT_CREATE_SPACE) + const [onClose, setOnClose] = useState void)>() + const iframe = useRef(null) const FLATFILE_PROVIDER_CONFIG = { ...config, ...configDefaults } @@ -217,15 +226,9 @@ export const FlatfileProvider: React.FC = ({ } // Works but only after the iframe is visible } - } - const styleSheetRef = useRef(false) - useEffect(() => { - if (!styleSheetRef.current) { - attachStyleSheet(config?.styleSheetOptions) - styleSheetRef.current = true - } - }, [config?.styleSheetOptions, styleSheetRef]) + onClose?.() + } // Listen to the postMessage event from the created iFrame useEffect(() => { @@ -280,12 +283,14 @@ export const FlatfileProvider: React.FC = ({ }, [ready, open]) const providerValue = useMemo( - () => ({ + (): FlatfileContextType => ({ ...(publishableKey ? { publishableKey } : {}), ...(internalAccessToken ? { accessToken: internalAccessToken } : {}), apiUrl, environmentId, open, + onClose, + setOnClose, setOpen, sessionSpace, setSessionSpace, @@ -319,6 +324,8 @@ export const FlatfileProvider: React.FC = ({ ready, iframe, FLATFILE_PROVIDER_CONFIG, + onClose, + setOnClose, ] ) diff --git a/packages/react/src/components/_tests_/FlatfileProvider.spec.tsx b/packages/react/src/components/_tests_/FlatfileProvider.spec.tsx index c68e3a98..fd615cb1 100644 --- a/packages/react/src/components/_tests_/FlatfileProvider.spec.tsx +++ b/packages/react/src/components/_tests_/FlatfileProvider.spec.tsx @@ -13,6 +13,8 @@ export const FlatfileProviderValue: FlatfileContextType = { apiUrl: '', open: false, setOpen: jest.fn(), + onClose: undefined, + setOnClose: jest.fn(), setSessionSpace: jest.fn(), listener: new FlatfileListener(), setListener: jest.fn(), diff --git a/packages/react/src/components/embeddedStyles.tsx b/packages/react/src/components/embeddedStyles.tsx index b83dfb66..ab472415 100644 --- a/packages/react/src/components/embeddedStyles.tsx +++ b/packages/react/src/components/embeddedStyles.tsx @@ -17,16 +17,19 @@ export const getContainerStyles = (isModal: boolean): React.CSSProperties => { return isModal ? { backgroundColor: 'rgba(0, 0, 0, 0.1)', + boxSizing: 'border-box', display: 'flex', - height: 'calc(100vh - 40px)', - width: 'calc(100% - 100px)', + height: '100dvh', + width: '100dvw', + left: '0', + top: '0', padding: '50px', position: 'fixed', zIndex: '1000', } : { - width: '100%', height: '100%', + width: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', diff --git a/packages/react/src/components/style.scss b/packages/react/src/components/style.scss index 0d49273a..580949c1 100644 --- a/packages/react/src/components/style.scss +++ b/packages/react/src/components/style.scss @@ -39,6 +39,7 @@ } .flatfile_iframe-wrapper { + box-sizing: border-box; color: var(--ff-text-color); font-family: var(--ff-text-font); font-size: var(--size-base); @@ -46,6 +47,12 @@ margin: 0; transition: opacity 0.25s ease-in-out; } +.flatfile_iframe-wrapper.flatfile_displayAsModal { + height: 100%; + height: 100vh; + width: 100%; + width: 100vw; +} .flatfile-close-button svg { fill: lightgray; width: 10px; @@ -167,11 +174,11 @@ box-sizing: border-box; display: block; height: 100%; - left: 0px; + left: 0; overflow-y: auto; - position: fixed; - right: 0px; + position: absolute; tab-size: 4; + top: 0; width: 100%; z-index: 1200; } diff --git a/packages/react/src/hooks/legacy/usePortal.tsx b/packages/react/src/hooks/legacy/usePortal.tsx index 777628da..ef15f945 100644 --- a/packages/react/src/hooks/legacy/usePortal.tsx +++ b/packages/react/src/hooks/legacy/usePortal.tsx @@ -1,23 +1,24 @@ -import React, { JSX, useEffect, useState } from 'react' -import DefaultError from '../../components/legacy/Error' -import Space from '../../components/legacy/LegacySpace' -import Spinner from '../../components/Spinner' +// Bug where the default export function is not being used properly in some build tooling +import { FlatfileClient } from '@flatfile/api' import { - State, - JobHandler, - SheetHandler, createWorkbookFromSheet, DefaultSubmitSettings, + JobHandler, + SheetHandler, + State, } from '@flatfile/embedded-utils' -import { initializeSpace } from '../../utils/initializeSpace' -import { getSpace } from '../../utils/getSpace' import { FlatfileRecord } from '@flatfile/hooks' import { FlatfileEvent, FlatfileListener } from '@flatfile/listener' import { recordHook } from '@flatfile/plugin-record-hook' +import React, { JSX, useEffect, useState } from 'react' +import DefaultError from '../../components/legacy/Error' +import Space from '../../components/legacy/LegacySpace' +import Spinner from '../../components/Spinner' import { IReactSimpleOnboarding } from '../../types/IReactSimpleOnboarding' +import { useAttachStyleSheet } from '../../utils/attachStyleSheet' +import { getSpace } from '../../utils/getSpace' +import { initializeSpace } from '../../utils/initializeSpace' -// Bug where the default export function is not being used properly in some build tooling -import { FlatfileClient } from '@flatfile/api' const api = new FlatfileClient() /** * @deprecated - use FlatfileProvider and Space components instead @@ -26,6 +27,7 @@ const api = new FlatfileClient() export const usePortal = ( props: IReactSimpleOnboarding ): JSX.Element | null => { + useAttachStyleSheet() const { errorTitle, loading: LoadingElement, apiUrl } = props const [initError, setInitError] = useState() const [state, setState] = useState({ diff --git a/packages/react/src/hooks/legacy/useSpace.tsx b/packages/react/src/hooks/legacy/useSpace.tsx index 3fa351c3..bab7e1a5 100644 --- a/packages/react/src/hooks/legacy/useSpace.tsx +++ b/packages/react/src/hooks/legacy/useSpace.tsx @@ -1,17 +1,19 @@ +import { State } from '@flatfile/embedded-utils' import React, { JSX, useEffect, useState } from 'react' import DefaultError from '../../components/legacy/Error' import Space from '../../components/legacy/LegacySpace' import Spinner from '../../components/Spinner' -import { State } from '@flatfile/embedded-utils' -import { initializeSpace } from '../../utils/initializeSpace' -import { getSpace } from '../../utils/getSpace' import { IReactSpaceProps } from '../../types' +import { useAttachStyleSheet } from '../../utils/attachStyleSheet' +import { getSpace } from '../../utils/getSpace' +import { initializeSpace } from '../../utils/initializeSpace' /** * @deprecated - use FlatfileProvider and Space components instead * This hook is used to initialize a space and return the Space component */ export const useSpace = (props: IReactSpaceProps): JSX.Element | null => { + useAttachStyleSheet() const { error: ErrorElement, errorTitle, diff --git a/packages/react/src/hooks/legacy/useSpaceTrigger.tsx b/packages/react/src/hooks/legacy/useSpaceTrigger.tsx index 712f17c8..36a6b5cb 100644 --- a/packages/react/src/hooks/legacy/useSpaceTrigger.tsx +++ b/packages/react/src/hooks/legacy/useSpaceTrigger.tsx @@ -1,11 +1,12 @@ +import { State } from '@flatfile/embedded-utils' import React, { JSX, useState } from 'react' import DefaultError from '../../components/legacy/Error' import Space from '../../components/legacy/LegacySpace' import Spinner from '../../components/Spinner' -import { State } from '@flatfile/embedded-utils' -import { initializeSpace } from '../../utils/initializeSpace' -import { getSpace } from '../../utils/getSpace' import { IReactSpaceProps } from '../../types' +import { useAttachStyleSheet } from '../../utils/attachStyleSheet' +import { getSpace } from '../../utils/getSpace' +import { initializeSpace } from '../../utils/initializeSpace' type IUseSpace = { OpenEmbed: () => Promise; Space: () => JSX.Element } @@ -15,6 +16,7 @@ type IUseSpace = { OpenEmbed: () => Promise; Space: () => JSX.Element } */ export const initializeFlatfile = (props: IReactSpaceProps): IUseSpace => { + useAttachStyleSheet() const { error: ErrorElement, errorTitle, loading: LoadingElement } = props const [initError, setInitError] = useState() const [loading, setLoading] = useState(false) diff --git a/packages/react/src/hooks/useFlatfile.ts b/packages/react/src/hooks/useFlatfile.ts index f54d6558..1f7fbf8b 100644 --- a/packages/react/src/hooks/useFlatfile.ts +++ b/packages/react/src/hooks/useFlatfile.ts @@ -1,31 +1,49 @@ -import { useContext } from 'react' +import { useCallback, useContext, useEffect } from 'react' import FlatfileContext from '../components/FlatfileContext' import { ClosePortalOptions } from '../types' -export const useFlatfile: () => { +interface UseFlatfileOptions { + onClose?: () => void +} + +export const useFlatfile: (useFlatfileOptions?: UseFlatfileOptions) => { openPortal: () => void closePortal: (options?: ClosePortalOptions) => void open: boolean setListener: (listener: any) => void listener: any -} = () => { +} = (useFlatfileOptions: UseFlatfileOptions = {}) => { const context = useContext(FlatfileContext) if (!context) { throw new Error('useFlatfile must be used within a FlatfileProvider') } + const onCloseCallback = useCallback( + useFlatfileOptions.onClose ?? (() => {}), + [typeof useFlatfileOptions.onClose] + ) + + useEffect(() => { + if (context.onClose !== onCloseCallback) { + context.setOnClose(() => onCloseCallback) + } + }, [context.onClose, context.setOnClose, onCloseCallback]) + const { open, setOpen, setListener, listener, apiUrl, resetSpace, ready } = context - const openPortal = () => { + const openPortal = useCallback(() => { ;(window as any).CROSSENV_FLATFILE_API_URL = apiUrl setOpen(true) - } + }, [setOpen, apiUrl]) - const closePortal = (options?: ClosePortalOptions) => { - resetSpace(options) - } + const closePortal = useCallback( + (options?: ClosePortalOptions) => { + resetSpace(options) + }, + [resetSpace] + ) return { openPortal, diff --git a/packages/react/src/utils/attachStyleSheet.ts b/packages/react/src/utils/attachStyleSheet.ts index bf141429..28186d72 100644 --- a/packages/react/src/utils/attachStyleSheet.ts +++ b/packages/react/src/utils/attachStyleSheet.ts @@ -1,6 +1,7 @@ import { styleInject } from '../utils/styleInject' import stylesheet from '../components/style.scss' +import { MutableRefObject } from 'react' export type StyleSheetOptions = { insertAt?: 'top' nonce?: string @@ -9,3 +10,12 @@ export type StyleSheetOptions = { export function attachStyleSheet(options?: StyleSheetOptions) { styleInject(stylesheet, options) } + +const styleSheetAttachedRef: MutableRefObject = { current: false } + +export function useAttachStyleSheet(options?: StyleSheetOptions) { + if (!styleSheetAttachedRef.current) { + attachStyleSheet(options) + styleSheetAttachedRef.current = true + } +}