import { FileType, ProjectPlugin, ProjectPluginStructure } from '@teleporthq/teleport-types' import { PageTransition } from '@teleporthq/teleport-shared' import { injectImportIntoApp } from '../app-import-injection' import { emitLegacyPeerDepsNpmrc } from '../npmrc-legacy-peer-deps' import { generatePageTransitionComponentCode } from './page-transition-component' const PAGE_MOUNT = '' const WRAPPED_PAGE_MOUNT = '' const IMPORT_LINE = "import TqPageTransition from '../components/tq-page-transition';" /** The resolved transition the editor exported, or null when pages switch instantly. */ export const projectPageTransition = PageTransition.projectPageTransition /** * Wraps the page mount in `_app` with the site's route transition — only when * the project chose one, so every other project's `_app` is byte-identical. * framer-motion (and the React 18 it needs) is registered the same way the * motion widget does it, since a site can transition without a single Motion * element on it. */ export class NextPageTransitionProjectPlugin implements ProjectPlugin { async runBefore(structure: ProjectPluginStructure): Promise { return structure } async runAfter(structure: ProjectPluginStructure): Promise { const transition = projectPageTransition(structure.uidl) if (!transition) { return structure } structure.files.set('tq-page-transition', { path: ['components'], files: [ { name: 'tq-page-transition', fileType: FileType.JS, content: generatePageTransitionComponentCode(transition), }, ], }) structure.dependencies['framer-motion'] = '^11.18.0' structure.dependencies.react = '^18.3.1' structure.dependencies['react-dom'] = '^18.3.1' emitLegacyPeerDepsNpmrc(structure, 'tq-page-transition-npmrc') const appFile = findAppFile(structure) if (!appFile || appFile.content.includes(WRAPPED_PAGE_MOUNT)) { return structure } if (!appFile.content.includes(PAGE_MOUNT)) { return structure } appFile.content = appFile.content.replace(PAGE_MOUNT, WRAPPED_PAGE_MOUNT) injectImportIntoApp(structure, IMPORT_LINE) return structure } } const findAppFile = (structure: ProjectPluginStructure): { content: string } | null => { for (const [key, record] of Array.from(structure.files.entries())) { if (key === '_app' || key.includes('_app')) { const candidate = record.files?.find( (file) => file.name === '_app' && (file.fileType === 'js' || file.fileType === 'tsx') ) if (candidate && typeof candidate.content === 'string') { return candidate } } } return null }