diff --git a/instances/form/complex-form.json b/instances/form/complex-form.json index f34aa61..fc576f3 100644 --- a/instances/form/complex-form.json +++ b/instances/form/complex-form.json @@ -148,7 +148,6 @@ "socialName": { "type": "object", "x-component": "FormField", - "x-custom": true, "x-ui": { "order": 5 }, diff --git a/packages/adapters/react/src/provider.test.tsx b/packages/adapters/react/src/provider.test.tsx index 2c58eba..50df84e 100644 --- a/packages/adapters/react/src/provider.test.tsx +++ b/packages/adapters/react/src/provider.test.tsx @@ -33,7 +33,7 @@ describe('ScheptaProvider', () => { it('should provide components configuration', () => { const componentSpec = createComponentSpec({ id: 'TestComponent', - factory: () => null, + component: () => null, type: 'field', }); @@ -60,13 +60,13 @@ describe('ScheptaProvider', () => { it('should support nested providers with hierarchical merge', () => { const ParentComponent = createComponentSpec({ id: 'ParentComponent', - factory: () => null, + component: () => null, type: 'field', }); const ChildComponent = createComponentSpec({ id: 'ChildComponent', - factory: () => null, + component: () => null, type: 'field', }); diff --git a/packages/adapters/react/src/runtime-adapter.tsx b/packages/adapters/react/src/runtime-adapter.tsx index 8da0e50..1d7ca3e 100644 --- a/packages/adapters/react/src/runtime-adapter.tsx +++ b/packages/adapters/react/src/runtime-adapter.tsx @@ -12,6 +12,7 @@ import type { RuntimeAdapter, ComponentSpec, RenderResult, RendererSpec } from ' */ export class ReactRuntimeAdapter implements RuntimeAdapter { create(spec: ComponentSpec | RendererSpec, props: Record): RenderResult { + if (spec.component) { const component = spec.component(props, this); // If factory returns a React component type, create element if (typeof component === 'function' || typeof component === 'object') { @@ -19,6 +20,9 @@ export class ReactRuntimeAdapter implements RuntimeAdapter { } // If it's already an element, return it return component as RenderResult; + } else { + throw new Error(`Component ${spec.id} is not a function`); + } } fragment(children: RenderResult[]): RenderResult { diff --git a/packages/core/src/orchestrators/component-orchestrator.ts b/packages/core/src/orchestrators/component-orchestrator.ts index 3e8e6a6..a383a86 100644 --- a/packages/core/src/orchestrators/component-orchestrator.ts +++ b/packages/core/src/orchestrators/component-orchestrator.ts @@ -55,12 +55,19 @@ export function resolveSpec( const componentName = schema['x-component'] || componentKey; const isCustomComponent = schema['x-custom'] === true; - let componentSpec = null; + let componentSpec: ComponentSpec | null = null; - if (isCustomComponent && customComponents) { + if (isCustomComponent && customComponents?.[componentKey]) { componentSpec = customComponents[componentKey]; - } else { - componentSpec = components[componentName]; + } + if (!componentSpec) { + componentSpec = components[componentName] ?? null; + } + + if (componentSpec && isCustomComponent && !customComponents?.[componentKey] && debugEnabled) { + console.warn( + `x-custom is set but no custom component registered for "${componentKey}"; using "${componentName}" from registry.` + ); } if (!componentSpec) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 690aa8b..6be8abe 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -293,7 +293,7 @@ importers: specifier: ^2.8.2 version: 2.10.9(@emotion/react@11.14.0)(@emotion/styled@11.14.1)(@types/react@18.3.27)(framer-motion@10.18.0)(react-dom@18.3.1)(react@18.3.1) '@emotion/react': - specifier: ^11.11.1 + specifier: ^11.11.0 version: 11.14.0(@types/react@18.3.27)(react@18.3.1) '@emotion/styled': specifier: ^11.11.0 @@ -313,6 +313,9 @@ importers: '@schepta/factory-react': specifier: workspace:* version: link:../packages/factories/react + '@tanstack/react-router': + specifier: ^1.159.5 + version: 1.159.5(react-dom@18.3.1)(react@18.3.1) formik: specifier: ^2.4.6 version: 2.4.9(@types/react@18.3.27)(react@18.3.1) @@ -329,11 +332,20 @@ importers: specifier: ^7.52.2 version: 7.68.0(react@18.3.1) react-icons: - specifier: ^5.5.0 + specifier: ^5.0.0 version: 5.5.0(react@18.3.1) - react-router-dom: - specifier: ^6.21.0 - version: 6.30.2(react-dom@18.3.1)(react@18.3.1) + single-spa: + specifier: ^6.0.1 + version: 6.0.3 + single-spa-react: + specifier: ^6.0.0 + version: 6.0.2(@types/react-dom@18.3.7)(@types/react@18.3.27)(react@18.3.1) + single-spa-vue: + specifier: ^3.0.1 + version: 3.0.1 + vue: + specifier: ^3.4.0 + version: 3.5.25(typescript@5.9.3) devDependencies: '@types/react': specifier: ^18.2.47 @@ -344,12 +356,18 @@ importers: '@vitejs/plugin-react': specifier: ^4.2.1 version: 4.7.0(vite@5.4.21) + '@vitejs/plugin-vue': + specifier: ^5.0.0 + version: 5.2.4(vite@5.4.21)(vue@3.5.25) typescript: specifier: ^5.3.3 version: 5.9.3 vite: specifier: ^5.0.8 version: 5.4.21(@types/node@20.19.25) + vite-plugin-single-spa: + specifier: ^1.1.0 + version: 1.1.0(vite@5.4.21) tests: devDependencies: @@ -1942,6 +1960,57 @@ packages: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true + /@tanstack/history@1.154.14: + resolution: {integrity: sha512-xyIfof8eHBuub1CkBnbKNKQXeRZC4dClhmzePHVOEel4G7lk/dW+TQ16da7CFdeNLv6u6Owf5VoBQxoo6DFTSA==} + engines: {node: '>=12'} + dev: false + + /@tanstack/react-router@1.159.5(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-rVb0MtKzP5c0BkWIoFgWBiRAJHYSU3bhsEHbT0cRdRLmlJiw21Awb6VEjgYq3hJiEhowcKKm6J8AdRD/8oZ5dQ==} + engines: {node: '>=12'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + dependencies: + '@tanstack/history': 1.154.14 + '@tanstack/react-store': 0.8.0(react-dom@18.3.1)(react@18.3.1) + '@tanstack/router-core': 1.159.4 + isbot: 5.1.35 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + dev: false + + /@tanstack/react-store@0.8.0(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-1vG9beLIuB7q69skxK9r5xiLN3ztzIPfSQSs0GfeqWGO2tGIyInZx0x1COhpx97RKaONSoAb8C3dxacWksm1ow==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + dependencies: + '@tanstack/store': 0.8.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + use-sync-external-store: 1.6.0(react@18.3.1) + dev: false + + /@tanstack/router-core@1.159.4: + resolution: {integrity: sha512-MFzPH39ijNO83qJN3pe7x4iAlhZyqgao3sJIzv3SJ4Pnk12xMnzuDzIAQT/1WV6JolPQEcw0Wr4L5agF8yxoeg==} + engines: {node: '>=12'} + dependencies: + '@tanstack/history': 1.154.14 + '@tanstack/store': 0.8.0 + cookie-es: 2.0.0 + seroval: 1.5.0 + seroval-plugins: 1.5.0(seroval@1.5.0) + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + dev: false + + /@tanstack/store@0.8.0: + resolution: {integrity: sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ==} + dev: false + /@testing-library/dom@9.3.4: resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==} engines: {node: '>=14'} @@ -2104,7 +2173,6 @@ packages: '@types/react': ^18.0.0 dependencies: '@types/react': 18.3.27 - dev: true /@types/react-transition-group@4.4.12(@types/react@18.3.27): resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==} @@ -2626,6 +2694,10 @@ packages: balanced-match: 1.0.2 dev: true + /browserslist-config-single-spa@1.0.1: + resolution: {integrity: sha512-nqOxTbatv6FcdgBvUTuH4MuojMZwvskspz5Y4dmpVcKd0uaQY8KEl3iALWus16+AwPVe3BIerBNEgELyaHZcQg==} + dev: false + /browserslist@4.28.1: resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -2866,6 +2938,10 @@ packages: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true + /cookie-es@2.0.0: + resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} + dev: false + /copy-anything@4.0.5: resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} engines: {node: '>=18'} @@ -3770,6 +3846,11 @@ packages: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} dev: true + /isbot@5.1.35: + resolution: {integrity: sha512-waFfC72ZNfwLLuJ2iLaoVaqcNo+CAaLR7xCpAn0Y5WfGzkNHv7ZN39Vbi1y+kb+Zs46XHOX3tZNExroFUPX+Kg==} + engines: {node: '>=18'} + dev: false + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true @@ -4778,6 +4859,20 @@ packages: engines: {node: '>=10'} hasBin: true + /seroval-plugins@1.5.0(seroval@1.5.0): + resolution: {integrity: sha512-EAHqADIQondwRZIdeW2I636zgsODzoBDwb3PT/+7TLDWyw1Dy/Xv7iGUIEXXav7usHDE9HVhOU61irI3EnyyHA==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + dependencies: + seroval: 1.5.0 + dev: false + + /seroval@1.5.0: + resolution: {integrity: sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw==} + engines: {node: '>=10'} + dev: false + /set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -4879,6 +4974,32 @@ packages: engines: {node: '>=14'} dev: true + /single-spa-react@6.0.2(@types/react-dom@18.3.7)(@types/react@18.3.27)(react@18.3.1): + resolution: {integrity: sha512-nRz5Izf57iYZXMXdTh+RmHnxxQojFL8NCIVUyA7PpXRib1ykmbat6JgT3yFPgXRs129BCC4+j+O5TLQOmLzF4Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: '*' + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@types/react': 18.3.27 + '@types/react-dom': 18.3.7(@types/react@18.3.27) + browserslist-config-single-spa: 1.0.1 + react: 18.3.1 + dev: false + + /single-spa-vue@3.0.1: + resolution: {integrity: sha512-GX6uQBBMGG3/ZQh6Z+tuqKGIuWKMbDhj3O/6xwX4j0NQJhW9kynHXbOWwwel1DFq6tkjzk7a0qBlhJwRCgn/lg==} + dev: false + + /single-spa@6.0.3: + resolution: {integrity: sha512-pXlwHXHhs3hx/MnGQHBLdFCVAN2rlm9TLBKazbDMyZ25QRxoen2vlUZ47MyXKa9K8Gd5UZs/gkuQad4TGbViwg==} + dev: false + /source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -5047,6 +5168,10 @@ packages: any-promise: 1.3.0 dev: true + /tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + dev: false + /tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} dev: false @@ -5382,6 +5507,14 @@ packages: tslib: 2.8.1 dev: false + /use-sync-external-store@1.6.0(react@18.3.1): + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + dependencies: + react: 18.3.1 + dev: false + /vfile-message@4.0.3: resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} dependencies: @@ -5418,6 +5551,14 @@ packages: - terser dev: true + /vite-plugin-single-spa@1.1.0(vite@5.4.21): + resolution: {integrity: sha512-L6an1fWRcMtr2xQ7ljnzDVQv241E6R9VwTP09lbK3wo/1/MWm3aHJmRD1rLgbVDTGsRQWfaYInwjirfUxNlkwQ==} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + dependencies: + vite: 5.4.21(@types/node@20.19.25) + dev: true + /vite-plugin-vuetify@2.1.2(vite@5.4.21)(vue@3.5.25)(vuetify@3.11.2): resolution: {integrity: sha512-I/wd6QS+DO6lHmuGoi1UTyvvBTQ2KDzQZ9oowJQEJ6OcjWfJnscYXx2ptm6S7fJSASuZT8jGRBL3LV4oS3LpaA==} engines: {node: ^18.0.0 || >=20.0.0} diff --git a/showcases/index.html b/showcases/index.html index 1918fbc..6834e2e 100644 --- a/showcases/index.html +++ b/showcases/index.html @@ -2,12 +2,26 @@ - - schepta React Example + + Single-SPA + Vite Experiment + -
- + +
+ - diff --git a/showcases/package.json b/showcases/package.json index d4fdd6c..082cdd3 100644 --- a/showcases/package.json +++ b/showcases/package.json @@ -1,7 +1,7 @@ { "name": "showcases", "version": "0.1.0", - "private": true, + "type": "module", "scripts": { "dev": "vite", "build": "vite build", @@ -9,26 +9,32 @@ }, "dependencies": { "@chakra-ui/react": "^2.8.2", - "@emotion/react": "^11.11.1", + "@emotion/react": "^11.11.0", "@emotion/styled": "^11.11.0", "@hookform/resolvers": "^3.9.0", "@mui/material": "^5.15.0", "@schepta/adapter-react": "workspace:*", "@schepta/core": "workspace:*", "@schepta/factory-react": "workspace:*", + "@tanstack/react-router": "^1.159.5", "formik": "^2.4.6", "framer-motion": "^10.16.16", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.52.2", - "react-icons": "^5.5.0", - "react-router-dom": "^6.21.0" + "react-icons": "^5.0.0", + "single-spa": "^6.0.1", + "single-spa-react": "^6.0.0", + "single-spa-vue": "^3.0.1", + "vue": "^3.4.0" }, "devDependencies": { "@types/react": "^18.2.47", "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.2.1", + "@vitejs/plugin-vue": "^5.0.0", "typescript": "^5.3.3", - "vite": "^5.0.8" + "vite": "^5.0.8", + "vite-plugin-single-spa": "^1.1.0" } -} +} \ No newline at end of file diff --git a/showcases/src/App.tsx b/showcases/src/App.tsx deleted file mode 100644 index f65a230..0000000 --- a/showcases/src/App.tsx +++ /dev/null @@ -1,140 +0,0 @@ -import React from "react"; -import { - BrowserRouter, - Routes, - Route, - Link, - useLocation, -} from "react-router-dom"; -import { BasicFormPage } from "./basic-ui/pages/BasicFormPage"; -import { ChakraFormPage } from "./chakra-ui/pages/ChakraFormPage"; -import { MaterialFormPage } from "./material-ui/pages/MaterialFormPage"; -import { FaReact } from "react-icons/fa"; -import { SiChakraui, SiMui } from "react-icons/si"; -import { - AppBar, - Toolbar, - Typography, - Box, - Container, - Tabs, - Tab, -} from "@mui/material"; -import { ScheptaProvider } from "@schepta/adapter-react"; -import { components } from "./basic-ui/components/ComponentRegistry"; - -const navigationItems = [ - { path: "/basic", label: "Basic UI", icon: }, - { path: "/chakra-ui", label: "Chakra UI", icon: }, - { path: "/material-ui", label: "Material UI", icon: }, -]; - -function Header() { - const location = useLocation(); - const currentPath = location.pathname; - - return ( - - - - - Schepta React Showcases - - - - - {navigationItems.map((item) => ( - - {item.label} - {item.icon} - - } - value={item.path} - component={Link} - to={item.path} - /> - ))} - - - - - - ); -} - -function App() { - const labelMiddleware = (props: any) => { - if (props.label) { - return { ...props, label: `[Provider] ${props.label}` }; - } - return props; - }; - - return ( - - - -
- - - - } - /> - } - /> - } - /> - } - /> - - - - - - ); -} - -export default App; diff --git a/showcases/src/frameworks/react/App.tsx b/showcases/src/frameworks/react/App.tsx new file mode 100644 index 0000000..cbafa07 --- /dev/null +++ b/showcases/src/frameworks/react/App.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import { router } from "./router"; +import { RouterProvider } from "@tanstack/react-router"; + +function App() { + return ; +} + +export default App; diff --git a/showcases/src/frameworks/react/RootLayout.tsx b/showcases/src/frameworks/react/RootLayout.tsx new file mode 100644 index 0000000..ad4b357 --- /dev/null +++ b/showcases/src/frameworks/react/RootLayout.tsx @@ -0,0 +1,34 @@ +// react/RootLayout.tsx +import React from 'react'; +import { Outlet } from '@tanstack/react-router'; +import { ScheptaProvider } from '@schepta/adapter-react'; +import { components } from './basic-ui/components/ComponentRegistry'; +import { Box, Container } from '@mui/material'; +import { ReactHeader } from './components/ReactHeader'; + +export function RootLayout() { + const labelMiddleware = (props: any) => { + if (props.label) { + return { ...props, label: `[Provider] ${props.label}` }; + } + return props; + }; + + return ( + + + + + + + + + ); +} \ No newline at end of file diff --git a/showcases/src/basic-ui/components/ComponentRegistry.tsx b/showcases/src/frameworks/react/basic-ui/components/ComponentRegistry.tsx similarity index 100% rename from showcases/src/basic-ui/components/ComponentRegistry.tsx rename to showcases/src/frameworks/react/basic-ui/components/ComponentRegistry.tsx diff --git a/showcases/src/basic-ui/components/Forms/FormWithFormik.tsx b/showcases/src/frameworks/react/basic-ui/components/Forms/FormWithFormik.tsx similarity index 100% rename from showcases/src/basic-ui/components/Forms/FormWithFormik.tsx rename to showcases/src/frameworks/react/basic-ui/components/Forms/FormWithFormik.tsx diff --git a/showcases/src/basic-ui/components/Forms/FormWithRHF.tsx b/showcases/src/frameworks/react/basic-ui/components/Forms/FormWithRHF.tsx similarity index 100% rename from showcases/src/basic-ui/components/Forms/FormWithRHF.tsx rename to showcases/src/frameworks/react/basic-ui/components/Forms/FormWithRHF.tsx diff --git a/showcases/src/basic-ui/components/Forms/ModalForm.tsx b/showcases/src/frameworks/react/basic-ui/components/Forms/ModalForm.tsx similarity index 100% rename from showcases/src/basic-ui/components/Forms/ModalForm.tsx rename to showcases/src/frameworks/react/basic-ui/components/Forms/ModalForm.tsx diff --git a/showcases/src/basic-ui/components/Forms/NativeComplexForm.tsx b/showcases/src/frameworks/react/basic-ui/components/Forms/NativeComplexForm.tsx similarity index 100% rename from showcases/src/basic-ui/components/Forms/NativeComplexForm.tsx rename to showcases/src/frameworks/react/basic-ui/components/Forms/NativeComplexForm.tsx diff --git a/showcases/src/basic-ui/components/Forms/NativeForm.tsx b/showcases/src/frameworks/react/basic-ui/components/Forms/NativeForm.tsx similarity index 100% rename from showcases/src/basic-ui/components/Forms/NativeForm.tsx rename to showcases/src/frameworks/react/basic-ui/components/Forms/NativeForm.tsx diff --git a/showcases/src/basic-ui/components/Inputs/InputText.tsx b/showcases/src/frameworks/react/basic-ui/components/Inputs/InputText.tsx similarity index 100% rename from showcases/src/basic-ui/components/Inputs/InputText.tsx rename to showcases/src/frameworks/react/basic-ui/components/Inputs/InputText.tsx diff --git a/showcases/src/basic-ui/components/formik/FormikFieldRenderer.tsx b/showcases/src/frameworks/react/basic-ui/components/formik/FormikFieldRenderer.tsx similarity index 100% rename from showcases/src/basic-ui/components/formik/FormikFieldRenderer.tsx rename to showcases/src/frameworks/react/basic-ui/components/formik/FormikFieldRenderer.tsx diff --git a/showcases/src/basic-ui/components/formik/FormikFormContainer.tsx b/showcases/src/frameworks/react/basic-ui/components/formik/FormikFormContainer.tsx similarity index 100% rename from showcases/src/basic-ui/components/formik/FormikFormContainer.tsx rename to showcases/src/frameworks/react/basic-ui/components/formik/FormikFormContainer.tsx diff --git a/showcases/src/basic-ui/components/rhf/RHFFieldRenderer.tsx b/showcases/src/frameworks/react/basic-ui/components/rhf/RHFFieldRenderer.tsx similarity index 100% rename from showcases/src/basic-ui/components/rhf/RHFFieldRenderer.tsx rename to showcases/src/frameworks/react/basic-ui/components/rhf/RHFFieldRenderer.tsx diff --git a/showcases/src/basic-ui/components/rhf/RHFFormContainer.tsx b/showcases/src/frameworks/react/basic-ui/components/rhf/RHFFormContainer.tsx similarity index 100% rename from showcases/src/basic-ui/components/rhf/RHFFormContainer.tsx rename to showcases/src/frameworks/react/basic-ui/components/rhf/RHFFormContainer.tsx diff --git a/showcases/src/basic-ui/pages/BasicFormPage.tsx b/showcases/src/frameworks/react/basic-ui/pages/BasicFormPage.tsx similarity index 92% rename from showcases/src/basic-ui/pages/BasicFormPage.tsx rename to showcases/src/frameworks/react/basic-ui/pages/BasicFormPage.tsx index fbe3412..ccc1ca4 100644 --- a/showcases/src/basic-ui/pages/BasicFormPage.tsx +++ b/showcases/src/frameworks/react/basic-ui/pages/BasicFormPage.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { Tab, Tabs, Paper } from "@mui/material"; -import simpleFormSchema from "../../../../instances/form/simple-form.json"; -import complexFormSchema from "../../../../instances/form/complex-form.json"; +import simpleFormSchema from "../../../../../../instances/form/simple-form.json"; +import complexFormSchema from "../../../../../../instances/form/complex-form.json"; import { NativeForm } from "../components/Forms/NativeForm"; import { TabPanel } from "../../material-ui/pages/MaterialFormPage"; import { ModalForm } from "../components/Forms/ModalForm"; diff --git a/showcases/src/chakra-ui/components/ComponentRegistry.tsx b/showcases/src/frameworks/react/chakra-ui/components/ComponentRegistry.tsx similarity index 83% rename from showcases/src/chakra-ui/components/ComponentRegistry.tsx rename to showcases/src/frameworks/react/chakra-ui/components/ComponentRegistry.tsx index 8599d4f..be45935 100644 --- a/showcases/src/chakra-ui/components/ComponentRegistry.tsx +++ b/showcases/src/frameworks/react/chakra-ui/components/ComponentRegistry.tsx @@ -15,73 +15,73 @@ import { FormSectionGroup } from "./Containers/FormSectionGroup"; export const components = { 'FormContainer': createComponentSpec({ - id: "FormContainer", + id: "FormContainer-chakra-ui", type: "container", component: (props, runtime) => FormContainer, }), InputText: createComponentSpec({ - id: "InputText", + id: "InputText-chakra-ui", type: "field", component: (props, runtime) => InputText, }), InputSelect: createComponentSpec({ - id: "InputSelect", + id: "InputSelect-chakra-ui", type: "field", component: (props, runtime) => InputSelect, }), InputCheckbox: createComponentSpec({ - id: "InputCheckbox", + id: "InputCheckbox-chakra-ui", type: "field", component: (props, runtime) => InputCheckbox, }), InputPhone: createComponentSpec({ - id: "InputPhone", + id: "InputPhone-chakra-ui", type: "field", component: (props, runtime) => InputText, defaultProps: { type: "tel" }, }), InputTextarea: createComponentSpec({ - id: "InputTextarea", + id: "InputTextarea-chakra-ui", type: "field", component: (props, runtime) => InputTextarea, }), InputNumber: createComponentSpec({ - id: "InputNumber", + id: "InputNumber-chakra-ui", type: "field", component: (props, runtime) => InputNumber, }), InputDate: createComponentSpec({ - id: "InputDate", + id: "InputDate-chakra-ui", type: "field", component: (props, runtime) => InputDate, }), SubmitButton: createComponentSpec({ - id: "SubmitButton", + id: "SubmitButton-chakra-ui", type: 'content', component: (props, runtime) => SubmitButton, }), FormField: createComponentSpec({ - id: "FormField", + id: "FormField-chakra-ui", type: 'container', component: (props, runtime) => FormField, }), FormSectionContainer: createComponentSpec({ - id: "FormSectionContainer", + id: "FormSectionContainer-chakra-ui", type: "container", component: (props, runtime) => FormSectionContainer, }), FormSectionTitle: createComponentSpec({ - id: "FormSectionTitle", + id: "FormSectionTitle-chakra-ui", type: 'content', component: (props, runtime) => FormSectionTitle, }), FormSectionGroupContainer: createComponentSpec({ - id: "FormSectionGroupContainer", + id: "FormSectionGroupContainer-chakra-ui", type: 'container', component: (props, runtime) => FormSectionGroupContainer, }), FormSectionGroup: createComponentSpec({ - id: "FormSectionGroup", + id: "FormSectionGroup-chakra-ui", type: 'container', component: (props, runtime) => FormSectionGroup, }), diff --git a/showcases/src/chakra-ui/components/Containers/FormContainer.tsx b/showcases/src/frameworks/react/chakra-ui/components/Containers/FormContainer.tsx similarity index 100% rename from showcases/src/chakra-ui/components/Containers/FormContainer.tsx rename to showcases/src/frameworks/react/chakra-ui/components/Containers/FormContainer.tsx diff --git a/showcases/src/chakra-ui/components/Containers/FormField.tsx b/showcases/src/frameworks/react/chakra-ui/components/Containers/FormField.tsx similarity index 100% rename from showcases/src/chakra-ui/components/Containers/FormField.tsx rename to showcases/src/frameworks/react/chakra-ui/components/Containers/FormField.tsx diff --git a/showcases/src/chakra-ui/components/Containers/FormSectionContainer.tsx b/showcases/src/frameworks/react/chakra-ui/components/Containers/FormSectionContainer.tsx similarity index 100% rename from showcases/src/chakra-ui/components/Containers/FormSectionContainer.tsx rename to showcases/src/frameworks/react/chakra-ui/components/Containers/FormSectionContainer.tsx diff --git a/showcases/src/chakra-ui/components/Containers/FormSectionGroup.tsx b/showcases/src/frameworks/react/chakra-ui/components/Containers/FormSectionGroup.tsx similarity index 100% rename from showcases/src/chakra-ui/components/Containers/FormSectionGroup.tsx rename to showcases/src/frameworks/react/chakra-ui/components/Containers/FormSectionGroup.tsx diff --git a/showcases/src/chakra-ui/components/Containers/FormSectionGroupContainer.tsx b/showcases/src/frameworks/react/chakra-ui/components/Containers/FormSectionGroupContainer.tsx similarity index 100% rename from showcases/src/chakra-ui/components/Containers/FormSectionGroupContainer.tsx rename to showcases/src/frameworks/react/chakra-ui/components/Containers/FormSectionGroupContainer.tsx diff --git a/showcases/src/chakra-ui/components/Containers/FormSectionTitle.tsx b/showcases/src/frameworks/react/chakra-ui/components/Containers/FormSectionTitle.tsx similarity index 100% rename from showcases/src/chakra-ui/components/Containers/FormSectionTitle.tsx rename to showcases/src/frameworks/react/chakra-ui/components/Containers/FormSectionTitle.tsx diff --git a/showcases/src/chakra-ui/components/Form.tsx b/showcases/src/frameworks/react/chakra-ui/components/Form.tsx similarity index 100% rename from showcases/src/chakra-ui/components/Form.tsx rename to showcases/src/frameworks/react/chakra-ui/components/Form.tsx diff --git a/showcases/src/chakra-ui/components/Inputs/InputCheckbox.tsx b/showcases/src/frameworks/react/chakra-ui/components/Inputs/InputCheckbox.tsx similarity index 100% rename from showcases/src/chakra-ui/components/Inputs/InputCheckbox.tsx rename to showcases/src/frameworks/react/chakra-ui/components/Inputs/InputCheckbox.tsx diff --git a/showcases/src/chakra-ui/components/Inputs/InputDate.tsx b/showcases/src/frameworks/react/chakra-ui/components/Inputs/InputDate.tsx similarity index 100% rename from showcases/src/chakra-ui/components/Inputs/InputDate.tsx rename to showcases/src/frameworks/react/chakra-ui/components/Inputs/InputDate.tsx diff --git a/showcases/src/chakra-ui/components/Inputs/InputNumber.tsx b/showcases/src/frameworks/react/chakra-ui/components/Inputs/InputNumber.tsx similarity index 100% rename from showcases/src/chakra-ui/components/Inputs/InputNumber.tsx rename to showcases/src/frameworks/react/chakra-ui/components/Inputs/InputNumber.tsx diff --git a/showcases/src/chakra-ui/components/Inputs/InputPhone.tsx b/showcases/src/frameworks/react/chakra-ui/components/Inputs/InputPhone.tsx similarity index 100% rename from showcases/src/chakra-ui/components/Inputs/InputPhone.tsx rename to showcases/src/frameworks/react/chakra-ui/components/Inputs/InputPhone.tsx diff --git a/showcases/src/chakra-ui/components/Inputs/InputSelect.tsx b/showcases/src/frameworks/react/chakra-ui/components/Inputs/InputSelect.tsx similarity index 100% rename from showcases/src/chakra-ui/components/Inputs/InputSelect.tsx rename to showcases/src/frameworks/react/chakra-ui/components/Inputs/InputSelect.tsx diff --git a/showcases/src/chakra-ui/components/Inputs/InputText.tsx b/showcases/src/frameworks/react/chakra-ui/components/Inputs/InputText.tsx similarity index 100% rename from showcases/src/chakra-ui/components/Inputs/InputText.tsx rename to showcases/src/frameworks/react/chakra-ui/components/Inputs/InputText.tsx diff --git a/showcases/src/chakra-ui/components/Inputs/InputTextarea.tsx b/showcases/src/frameworks/react/chakra-ui/components/Inputs/InputTextarea.tsx similarity index 100% rename from showcases/src/chakra-ui/components/Inputs/InputTextarea.tsx rename to showcases/src/frameworks/react/chakra-ui/components/Inputs/InputTextarea.tsx diff --git a/showcases/src/chakra-ui/components/SubmitButton.tsx b/showcases/src/frameworks/react/chakra-ui/components/SubmitButton.tsx similarity index 100% rename from showcases/src/chakra-ui/components/SubmitButton.tsx rename to showcases/src/frameworks/react/chakra-ui/components/SubmitButton.tsx diff --git a/showcases/src/chakra-ui/pages/ChakraFormPage.tsx b/showcases/src/frameworks/react/chakra-ui/pages/ChakraFormPage.tsx similarity index 84% rename from showcases/src/chakra-ui/pages/ChakraFormPage.tsx rename to showcases/src/frameworks/react/chakra-ui/pages/ChakraFormPage.tsx index 1bbca62..306fcd8 100644 --- a/showcases/src/chakra-ui/pages/ChakraFormPage.tsx +++ b/showcases/src/frameworks/react/chakra-ui/pages/ChakraFormPage.tsx @@ -10,12 +10,13 @@ import { } from "@chakra-ui/react"; import { Form } from "../components/Form"; import { FormSchema } from "@schepta/core"; -import simpleFormSchema from "../../../../instances/form/simple-form.json"; -import complexFormSchema from "../../../../instances/form/complex-form.json"; +import simpleFormSchema from "../../../../../../instances/form/simple-form.json"; +import complexFormSchema from "../../../../../../instances/form/complex-form.json"; export function ChakraFormPage() { const simpleSchema = simpleFormSchema as FormSchema; const complexSchema = complexFormSchema as FormSchema; + return ( + + + + Single-SPA + Vite Experiment + + + + + Home + + + React + + + Vue + + + Vanilla + + + + + + ); +} diff --git a/showcases/src/frameworks/react/components/ReactHeader.tsx b/showcases/src/frameworks/react/components/ReactHeader.tsx new file mode 100644 index 0000000..00aaa70 --- /dev/null +++ b/showcases/src/frameworks/react/components/ReactHeader.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { Link, useRouterState } from '@tanstack/react-router'; +import { + AppBar, + Toolbar, + Typography, + Box, + Container, + Tabs, + Tab, +} from '@mui/material'; +import { FaReact } from 'react-icons/fa'; +import { SiChakraui, SiMui } from 'react-icons/si'; + +const base = '/react'; +const navigationItems = [ + { path: `${base}/basic`, label: 'Basic UI', icon: }, + { path: `${base}/chakra-ui`, label: 'Chakra UI', icon: }, + { path: `${base}/material-ui`, label: 'Material UI', icon: }, +]; + +export function ReactHeader() { + const router = useRouterState(); + const currentPath = router.location.pathname; + + return ( + + + + + Schepta React Showcases + + + + {navigationItems.map((item) => ( + + {item.label} + {item.icon} + + } + value={item.path} + component={Link} + to={item.path} + /> + ))} + + + + + + ); +} \ No newline at end of file diff --git a/showcases/src/index.css b/showcases/src/frameworks/react/index.css similarity index 100% rename from showcases/src/index.css rename to showcases/src/frameworks/react/index.css diff --git a/showcases/src/frameworks/react/index.tsx b/showcases/src/frameworks/react/index.tsx new file mode 100644 index 0000000..ce4490f --- /dev/null +++ b/showcases/src/frameworks/react/index.tsx @@ -0,0 +1,26 @@ +import React from "react"; +import ReactDOMClient from "react-dom/client"; +import App from "./App"; +import './index.css'; +import singleSpaReact from "single-spa-react"; +import { HomePage } from "./pages/HomePage"; + +export const lifecycles = singleSpaReact({ + React, + ReactDOMClient, + rootComponent: App, + errorBoundary(err, info, props) { + return ( +
+

Something went wrong

+
{err?.message}
+
+ ); + }, +}); + +export const homeLifecycles = singleSpaReact({ + React, + ReactDOMClient, + rootComponent: HomePage, +}); \ No newline at end of file diff --git a/showcases/src/material-ui/components/ComponentRegistry.tsx b/showcases/src/frameworks/react/material-ui/components/ComponentRegistry.tsx similarity index 82% rename from showcases/src/material-ui/components/ComponentRegistry.tsx rename to showcases/src/frameworks/react/material-ui/components/ComponentRegistry.tsx index 8599d4f..4f66476 100644 --- a/showcases/src/material-ui/components/ComponentRegistry.tsx +++ b/showcases/src/frameworks/react/material-ui/components/ComponentRegistry.tsx @@ -15,73 +15,73 @@ import { FormSectionGroup } from "./Containers/FormSectionGroup"; export const components = { 'FormContainer': createComponentSpec({ - id: "FormContainer", + id: "FormContainer-material-ui", type: "container", component: (props, runtime) => FormContainer, }), InputText: createComponentSpec({ - id: "InputText", + id: "InputText-material-ui", type: "field", component: (props, runtime) => InputText, }), InputSelect: createComponentSpec({ - id: "InputSelect", + id: "InputSelect-material-ui", type: "field", component: (props, runtime) => InputSelect, }), InputCheckbox: createComponentSpec({ - id: "InputCheckbox", + id: "InputCheckbox-material-ui", type: "field", component: (props, runtime) => InputCheckbox, }), InputPhone: createComponentSpec({ - id: "InputPhone", + id: "InputPhone-material-ui", type: "field", component: (props, runtime) => InputText, defaultProps: { type: "tel" }, }), InputTextarea: createComponentSpec({ - id: "InputTextarea", + id: "InputTextarea-material-ui", type: "field", component: (props, runtime) => InputTextarea, }), InputNumber: createComponentSpec({ - id: "InputNumber", + id: "InputNumber-material-ui", type: "field", component: (props, runtime) => InputNumber, }), InputDate: createComponentSpec({ - id: "InputDate", + id: "InputDate-material-ui", type: "field", component: (props, runtime) => InputDate, }), SubmitButton: createComponentSpec({ - id: "SubmitButton", + id: "SubmitButton-material-ui", type: 'content', component: (props, runtime) => SubmitButton, }), FormField: createComponentSpec({ - id: "FormField", + id: "FormField-material-ui", type: 'container', component: (props, runtime) => FormField, }), FormSectionContainer: createComponentSpec({ - id: "FormSectionContainer", + id: "FormSectionContainer-material-ui", type: "container", component: (props, runtime) => FormSectionContainer, }), FormSectionTitle: createComponentSpec({ - id: "FormSectionTitle", + id: "FormSectionTitle-material-ui", type: 'content', component: (props, runtime) => FormSectionTitle, }), FormSectionGroupContainer: createComponentSpec({ - id: "FormSectionGroupContainer", + id: "FormSectionGroupContainer-material-ui", type: 'container', component: (props, runtime) => FormSectionGroupContainer, }), FormSectionGroup: createComponentSpec({ - id: "FormSectionGroup", + id: "FormSectionGroup-material-ui", type: 'container', component: (props, runtime) => FormSectionGroup, }), diff --git a/showcases/src/material-ui/components/Containers/FormContainer.tsx b/showcases/src/frameworks/react/material-ui/components/Containers/FormContainer.tsx similarity index 100% rename from showcases/src/material-ui/components/Containers/FormContainer.tsx rename to showcases/src/frameworks/react/material-ui/components/Containers/FormContainer.tsx diff --git a/showcases/src/material-ui/components/Containers/FormField.tsx b/showcases/src/frameworks/react/material-ui/components/Containers/FormField.tsx similarity index 100% rename from showcases/src/material-ui/components/Containers/FormField.tsx rename to showcases/src/frameworks/react/material-ui/components/Containers/FormField.tsx diff --git a/showcases/src/material-ui/components/Containers/FormSectionContainer.tsx b/showcases/src/frameworks/react/material-ui/components/Containers/FormSectionContainer.tsx similarity index 100% rename from showcases/src/material-ui/components/Containers/FormSectionContainer.tsx rename to showcases/src/frameworks/react/material-ui/components/Containers/FormSectionContainer.tsx diff --git a/showcases/src/material-ui/components/Containers/FormSectionGroup.tsx b/showcases/src/frameworks/react/material-ui/components/Containers/FormSectionGroup.tsx similarity index 100% rename from showcases/src/material-ui/components/Containers/FormSectionGroup.tsx rename to showcases/src/frameworks/react/material-ui/components/Containers/FormSectionGroup.tsx diff --git a/showcases/src/material-ui/components/Containers/FormSectionGroupContainer.tsx b/showcases/src/frameworks/react/material-ui/components/Containers/FormSectionGroupContainer.tsx similarity index 100% rename from showcases/src/material-ui/components/Containers/FormSectionGroupContainer.tsx rename to showcases/src/frameworks/react/material-ui/components/Containers/FormSectionGroupContainer.tsx diff --git a/showcases/src/material-ui/components/Containers/FormSectionTitle.tsx b/showcases/src/frameworks/react/material-ui/components/Containers/FormSectionTitle.tsx similarity index 100% rename from showcases/src/material-ui/components/Containers/FormSectionTitle.tsx rename to showcases/src/frameworks/react/material-ui/components/Containers/FormSectionTitle.tsx diff --git a/showcases/src/material-ui/components/Form.tsx b/showcases/src/frameworks/react/material-ui/components/Form.tsx similarity index 100% rename from showcases/src/material-ui/components/Form.tsx rename to showcases/src/frameworks/react/material-ui/components/Form.tsx diff --git a/showcases/src/material-ui/components/Inputs/InputCheckbox.tsx b/showcases/src/frameworks/react/material-ui/components/Inputs/InputCheckbox.tsx similarity index 100% rename from showcases/src/material-ui/components/Inputs/InputCheckbox.tsx rename to showcases/src/frameworks/react/material-ui/components/Inputs/InputCheckbox.tsx diff --git a/showcases/src/material-ui/components/Inputs/InputDate.tsx b/showcases/src/frameworks/react/material-ui/components/Inputs/InputDate.tsx similarity index 100% rename from showcases/src/material-ui/components/Inputs/InputDate.tsx rename to showcases/src/frameworks/react/material-ui/components/Inputs/InputDate.tsx diff --git a/showcases/src/material-ui/components/Inputs/InputNumber.tsx b/showcases/src/frameworks/react/material-ui/components/Inputs/InputNumber.tsx similarity index 100% rename from showcases/src/material-ui/components/Inputs/InputNumber.tsx rename to showcases/src/frameworks/react/material-ui/components/Inputs/InputNumber.tsx diff --git a/showcases/src/material-ui/components/Inputs/InputPhone.tsx b/showcases/src/frameworks/react/material-ui/components/Inputs/InputPhone.tsx similarity index 100% rename from showcases/src/material-ui/components/Inputs/InputPhone.tsx rename to showcases/src/frameworks/react/material-ui/components/Inputs/InputPhone.tsx diff --git a/showcases/src/material-ui/components/Inputs/InputSelect.tsx b/showcases/src/frameworks/react/material-ui/components/Inputs/InputSelect.tsx similarity index 100% rename from showcases/src/material-ui/components/Inputs/InputSelect.tsx rename to showcases/src/frameworks/react/material-ui/components/Inputs/InputSelect.tsx diff --git a/showcases/src/material-ui/components/Inputs/InputText.tsx b/showcases/src/frameworks/react/material-ui/components/Inputs/InputText.tsx similarity index 100% rename from showcases/src/material-ui/components/Inputs/InputText.tsx rename to showcases/src/frameworks/react/material-ui/components/Inputs/InputText.tsx diff --git a/showcases/src/material-ui/components/Inputs/InputTextarea.tsx b/showcases/src/frameworks/react/material-ui/components/Inputs/InputTextarea.tsx similarity index 100% rename from showcases/src/material-ui/components/Inputs/InputTextarea.tsx rename to showcases/src/frameworks/react/material-ui/components/Inputs/InputTextarea.tsx diff --git a/showcases/src/material-ui/components/SubmitButton.tsx b/showcases/src/frameworks/react/material-ui/components/SubmitButton.tsx similarity index 100% rename from showcases/src/material-ui/components/SubmitButton.tsx rename to showcases/src/frameworks/react/material-ui/components/SubmitButton.tsx diff --git a/showcases/src/material-ui/pages/MaterialFormPage.tsx b/showcases/src/frameworks/react/material-ui/pages/MaterialFormPage.tsx similarity index 90% rename from showcases/src/material-ui/pages/MaterialFormPage.tsx rename to showcases/src/frameworks/react/material-ui/pages/MaterialFormPage.tsx index db3c9f3..72310dd 100644 --- a/showcases/src/material-ui/pages/MaterialFormPage.tsx +++ b/showcases/src/frameworks/react/material-ui/pages/MaterialFormPage.tsx @@ -1,6 +1,6 @@ import { FormSchema } from "@schepta/core"; -import simpleFormSchema from "../../../../instances/form/simple-form.json"; -import complexFormSchema from "../../../../instances/form/complex-form.json"; +import simpleFormSchema from "../../../../../../instances/form/simple-form.json"; +import complexFormSchema from "../../../../../../instances/form/complex-form.json"; import { Paper, Box, Tabs, Tab } from "@mui/material"; import React, { useState } from "react"; import { Form } from "../components/Form"; diff --git a/showcases/src/frameworks/react/pages/HomePage.tsx b/showcases/src/frameworks/react/pages/HomePage.tsx new file mode 100644 index 0000000..e350df5 --- /dev/null +++ b/showcases/src/frameworks/react/pages/HomePage.tsx @@ -0,0 +1,96 @@ +import React from "react"; + +export function HomePage() { + return ( + + ); +} diff --git a/showcases/src/frameworks/react/pages/ReactFormPage.tsx b/showcases/src/frameworks/react/pages/ReactFormPage.tsx new file mode 100644 index 0000000..4427d15 --- /dev/null +++ b/showcases/src/frameworks/react/pages/ReactFormPage.tsx @@ -0,0 +1,60 @@ +import React, { useState } from "react"; +import { Tab, Tabs, Paper } from "@mui/material"; +import { FaReact } from "react-icons/fa"; + +interface TabPanelProps { + children?: React.ReactNode; + index: number; + value: number; +} + +function TabPanel({ children, value, index }: TabPanelProps) { + return ( + + ); +} + +export function ReactFormPage() { + const [tabValue, setTabValue] = useState(0); + + const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { + setTabValue(newValue); + }; + + return ( +
+
+ +

React Microfrontend - Schepta Forms

+
+ +
+ + + + + + + +

Simple React Form

+

Esta é uma demonstração básica do Schepta no React (monorepo unificado)

+
+

✅ Funcionando! Este microfrontend React está rodando dentro do shell principal.

+
+
+ + +

Complex React Form

+

Aqui seria um formulário mais complexo com Schepta

+
+
+
+
+ ); +} \ No newline at end of file diff --git a/showcases/src/frameworks/react/router.tsx b/showcases/src/frameworks/react/router.tsx new file mode 100644 index 0000000..ff5301e --- /dev/null +++ b/showcases/src/frameworks/react/router.tsx @@ -0,0 +1,65 @@ +// react/router.tsx +import { + createRouter, + createRoute, + createRootRoute, + Outlet, + redirect, +} from '@tanstack/react-router'; +import { BasicFormPage } from './basic-ui/pages/BasicFormPage'; +import { ChakraFormPage } from './chakra-ui/pages/ChakraFormPage'; +import { MaterialFormPage } from './material-ui/pages/MaterialFormPage'; +import { RootLayout } from './RootLayout'; + +const rootRoute = createRootRoute({ + component: () => , +}); + +const reactRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'react', + component: RootLayout, +}); + +const reactIndexRoute = createRoute({ + getParentRoute: () => reactRoute, + path: '/', + beforeLoad: () => { + throw redirect({ to: '/react/basic' }); + }, +}); + +const basicRoute = createRoute({ + getParentRoute: () => reactRoute, + path: 'basic', + component: BasicFormPage, +}); + +const chakraRoute = createRoute({ + getParentRoute: () => reactRoute, + path: 'chakra-ui', + component: ChakraFormPage, +}); + +const materialRoute = createRoute({ + getParentRoute: () => reactRoute, + path: 'material-ui', + component: MaterialFormPage, +}); + +const routeTree = rootRoute.addChildren([ + reactRoute.addChildren([ + reactIndexRoute, + basicRoute, + chakraRoute, + materialRoute, + ]), +]); + +export const router = createRouter({ routeTree }); + +declare module '@tanstack/react-router' { + interface Register { + router: typeof router; + } +} diff --git a/showcases/src/frameworks/vue/components/VueFormPage.vue b/showcases/src/frameworks/vue/components/VueFormPage.vue new file mode 100644 index 0000000..9ab7ad4 --- /dev/null +++ b/showcases/src/frameworks/vue/components/VueFormPage.vue @@ -0,0 +1,79 @@ + + + + + \ No newline at end of file diff --git a/showcases/src/frameworks/vue/index.ts b/showcases/src/frameworks/vue/index.ts new file mode 100644 index 0000000..a839bc7 --- /dev/null +++ b/showcases/src/frameworks/vue/index.ts @@ -0,0 +1,12 @@ +import { createApp, h } from 'vue'; +import VueFormPage from './components/VueFormPage.vue'; +import singleSpaVue from 'single-spa-vue'; + +const lifecycles = singleSpaVue({ + createApp, + appOptions: { + render: () => h(VueFormPage), + }, +}); + +export const { bootstrap, mount, unmount } = lifecycles; \ No newline at end of file diff --git a/showcases/src/main.ts b/showcases/src/main.ts new file mode 100644 index 0000000..1fd2399 --- /dev/null +++ b/showcases/src/main.ts @@ -0,0 +1,55 @@ +import { registerApplication, start } from 'single-spa'; +import ReactDOM from "react-dom/client"; +import { lifecycles as reactApp, homeLifecycles } from './frameworks/react'; +import React from 'react'; +import { Header } from './frameworks/react/components/Header'; + + +registerApplication({ + name: 'header', + app: () => Promise.resolve({ + bootstrap: () => Promise.resolve(), + mount: () => { + const root = ReactDOM.createRoot( + document.getElementById("header")!, + ); + root.render(React.createElement(Header)); + return Promise.resolve(); + }, + unmount: () => { + const container = document.getElementById('header'); + if (container) container.innerHTML = ''; + return Promise.resolve(); + } + }), + activeWhen: '/' +}); + +registerApplication({ + name: 'home', + app: () => Promise.resolve(homeLifecycles), + activeWhen: (location) => location.pathname === '/' +}); + +registerApplication({ + name: 'react', + app: () => Promise.resolve(reactApp), + activeWhen: (location) => location.pathname.startsWith('/react') +}); + +registerApplication({ + name: 'vue', + app: () => import('./frameworks/vue'), + activeWhen: (location) => location.pathname === '/vue' +}); + +registerApplication({ + name: 'vanilla', + app: () => import('./vanilla'), + activeWhen: (location) => location.pathname === '/vanilla', + customProps: { + domElementGetter: () => document.getElementById('vanilla')!, + }, +}); + +start(); \ No newline at end of file diff --git a/showcases/src/main.tsx b/showcases/src/main.tsx deleted file mode 100644 index 0c657b5..0000000 --- a/showcases/src/main.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import App from './App'; -import './index.css'; - -ReactDOM.createRoot(document.getElementById('root')!).render( - - - -); - diff --git a/showcases/src/vanilla/app.ts b/showcases/src/vanilla/app.ts new file mode 100644 index 0000000..6403b54 --- /dev/null +++ b/showcases/src/vanilla/app.ts @@ -0,0 +1,46 @@ +/** + * Creates the root DOM element for the vanilla app (layout, tabs, panels). + * Does not inject styles or attach event listeners. + */ +export function createAppRoot(): HTMLElement { + const container = document.createElement('div'); + container.style.cssText = ` + padding: 2rem; + background: #fffbf0; + border-radius: 8px; + margin: 1rem 0; + `; + + container.innerHTML = ` +
+ + + +

Vanilla JS Microfrontend - Schepta Forms

+
+ +
+
+ + +
+ +
+
+

Simple Vanilla Form

+

Esta é uma demonstração básica do Schepta em Vanilla JS (monorepo unificado)

+
+

✅ Funcionando! Este microfrontend Vanilla JS está rodando dentro do shell principal.

+
+
+ +
+

Complex Vanilla Form

+

Aqui seria um formulário mais complexo com Schepta

+
+
+
+ `; + + return container; +} diff --git a/showcases/src/vanilla/index.ts b/showcases/src/vanilla/index.ts new file mode 100644 index 0000000..c2b8a52 --- /dev/null +++ b/showcases/src/vanilla/index.ts @@ -0,0 +1,15 @@ +import { singleSpaVanilla } from './singleSpaVanilla'; +import { createAppRoot } from './app'; +import { vanillaStyles } from './styles'; +import { attachTabBehavior } from './tabs'; + +const lifecycles = singleSpaVanilla({ + render() { + const el = createAppRoot(); + attachTabBehavior(el); + return el; + }, + styles: vanillaStyles, +}); + +export const { bootstrap, mount, unmount } = lifecycles; diff --git a/showcases/src/vanilla/singleSpaVanilla.ts b/showcases/src/vanilla/singleSpaVanilla.ts new file mode 100644 index 0000000..40acc4d --- /dev/null +++ b/showcases/src/vanilla/singleSpaVanilla.ts @@ -0,0 +1,75 @@ +/** + * Helper for single-spa vanilla JS apps (no framework). + * Returns bootstrap, mount, unmount using render + optional styles + afterMount. + */ + +export interface SingleSpaVanillaProps { + name?: string; + singleSpa?: unknown; + mountParcel?: unknown; + customProps?: Record; + domElement?: HTMLElement; + domElementGetter?(): HTMLElement; +} + +export interface SingleSpaVanillaOptions { + /** Returns the root DOM element to mount. */ + render(props: SingleSpaVanillaProps): HTMLElement; + /** Optional CSS string injected into document.head on mount, removed on unmount. */ + styles?: string; + /** Optional: run after the root element is appended (e.g. attach event listeners). */ + afterMount?(element: HTMLElement, props: SingleSpaVanillaProps): void; +} + +export interface SingleSpaVanillaLifecycles { + bootstrap(props: SingleSpaVanillaProps): Promise; + mount(props: SingleSpaVanillaProps): Promise; + unmount(props: SingleSpaVanillaProps): Promise; +} + +export function singleSpaVanilla(options: SingleSpaVanillaOptions): SingleSpaVanillaLifecycles { + let rootElement: HTMLElement | null = null; + let styleElement: HTMLStyleElement | null = null; + + return { + bootstrap(): Promise { + return Promise.resolve(); + }, + + mount(props: SingleSpaVanillaProps): Promise { + const container = + props.domElement ?? + (typeof props.domElementGetter === 'function' ? props.domElementGetter() : null); + + if (!container) { + return Promise.resolve(); + } + + rootElement = options.render(props); + container.appendChild(rootElement); + + if (options.styles) { + styleElement = document.createElement('style'); + styleElement.textContent = options.styles; + document.head.appendChild(styleElement); + } + + options.afterMount?.(rootElement, props); + return Promise.resolve(); + }, + + unmount(): Promise { + if (rootElement?.parentNode) { + rootElement.parentNode.removeChild(rootElement); + } + rootElement = null; + + if (styleElement?.parentNode) { + styleElement.parentNode.removeChild(styleElement); + } + styleElement = null; + + return Promise.resolve(); + }, + }; +} diff --git a/showcases/src/vanilla/styles.ts b/showcases/src/vanilla/styles.ts new file mode 100644 index 0000000..6aa0ab0 --- /dev/null +++ b/showcases/src/vanilla/styles.ts @@ -0,0 +1,11 @@ +export const vanillaStyles = ` + .vanilla-tabs { display: flex; border-bottom: 1px solid #ddd; margin-bottom: 1rem; } + .vanilla-tab { padding: 0.5rem 1rem; border: none; background: none; cursor: pointer; border-bottom: 2px solid transparent; font-family: inherit; } + .vanilla-tab.active { border-bottom-color: #f7df1e; color: #333; font-weight: 600; } + .vanilla-tab:hover { background-color: #f5f5f5; } + .vanilla-tab-content { padding: 1rem 0; } + .vanilla-tab-panel { display: none; } + .vanilla-tab-panel.active { display: block; } + .vanilla-tab-panel h3 { margin-bottom: 0.5rem; color: #333; } + .vanilla-tab-panel p { color: #666; line-height: 1.5; } +`; diff --git a/showcases/src/vanilla/tabs.ts b/showcases/src/vanilla/tabs.ts new file mode 100644 index 0000000..442d17c --- /dev/null +++ b/showcases/src/vanilla/tabs.ts @@ -0,0 +1,22 @@ +/** + * Attaches tab click behavior to the root element: toggles .active on tabs and panels. + */ +export function attachTabBehavior(root: HTMLElement): void { + const tabs = root.querySelectorAll('.vanilla-tab'); + const panels = root.querySelectorAll('.vanilla-tab-panel'); + + tabs.forEach((tab) => { + tab.addEventListener('click', (e) => { + const target = e.target as HTMLButtonElement; + const targetTab = target.dataset.tab; + if (!targetTab) return; + + tabs.forEach((t) => t.classList.remove('active')); + panels.forEach((p) => p.classList.remove('active')); + + target.classList.add('active'); + const panel = root.querySelector(`#${targetTab}-content`); + panel?.classList.add('active'); + }); + }); +} diff --git a/showcases/tsconfig.json b/showcases/tsconfig.json index 87a21d8..e6a6f82 100644 --- a/showcases/tsconfig.json +++ b/showcases/tsconfig.json @@ -8,4 +8,4 @@ }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] -} \ No newline at end of file +} diff --git a/showcases/vite.config.ts b/showcases/vite.config.ts index 071fa49..1580497 100644 --- a/showcases/vite.config.ts +++ b/showcases/vite.config.ts @@ -1,10 +1,55 @@ import { defineConfig } from 'vite'; +import vitePluginSingleSpa from 'vite-plugin-single-spa'; import react from '@vitejs/plugin-react'; +import vue from '@vitejs/plugin-vue'; export default defineConfig({ - plugins: [react()], + plugins: [ + react(), + vue(), + vitePluginSingleSpa({ + type: 'root', + imo: '3.1.1' + }) + ], server: { - port: 3000, + port: 3000 }, -}); - + build: { + rollupOptions: { + output: { + manualChunks(id) { + // React core + if (id.includes('node_modules/react/') || id.includes('node_modules/react-dom/')) { + return 'react-vendor'; + } + // Vue + if (id.includes('node_modules/vue/') || id.includes('node_modules/@vue/')) { + return 'vue-vendor'; + } + // MUI + Emotion + if (id.includes('node_modules/@mui/') || id.includes('node_modules/@emotion/')) { + return 'mui-vendor'; + } + // Chakra UI + Framer Motion + if (id.includes('node_modules/@chakra-ui/') || id.includes('node_modules/framer-motion/')) { + return 'chakra-vendor'; + } + // TanStack Router + if (id.includes('node_modules/@tanstack/router')) { + return 'router-vendor'; + } + // single-spa + if (id.includes('node_modules/single-spa')) { + return 'single-spa-vendor'; + } + // Form libs (só carregados na rota /react) + if (id.includes('node_modules/react-hook-form') || id.includes('node_modules/formik') || id.includes('node_modules/@hookform')) { + return 'form-vendor'; + } + } + } + }, + chunkSizeWarningLimit: 400 + } +}); \ No newline at end of file diff --git a/tests/e2e/react.spec.ts b/tests/e2e/react.spec.ts index 5a5caf1..aaf8952 100644 --- a/tests/e2e/react.spec.ts +++ b/tests/e2e/react.spec.ts @@ -6,7 +6,7 @@ import { extractFieldsFromSchema, FormSchema } from '@schepta/core'; test.describe('React Form Factory', () => { test.beforeEach(async ({ page, baseURL }) => { - await page.goto(`${baseURL}`); + await page.goto(`${baseURL}react/basic`); }); test('should render simple form', async ({ page }) => { @@ -21,7 +21,7 @@ test.describe('React Form Factory', () => { } }); - test('should render complex form with all field types', async ({ page, baseURL }) => { + test('should render complex form with all field types', async ({ page }) => { await page.click('[data-test-id*="complex-form-tab"]'); const fields = extractFieldsFromSchema(complexFormSchema as FormSchema).filter(field => field.visible === true && field.custom === false); @@ -165,7 +165,7 @@ test.describe('React Form Factory', () => { test.describe('React Hook Form Integration', () => { test.beforeEach(async ({ page, baseURL }) => { - await page.goto(`${baseURL}`); + await page.goto(`${baseURL}react/basic`); // Navigate to RHF form tab await page.click('[data-test-id*="rhf-form-tab"]'); await page.waitForSelector('[data-test-id^="FormContainer"]', { timeout: 10000 }); @@ -200,7 +200,7 @@ test.describe('React Hook Form Integration', () => { test.describe('Formik Integration', () => { test.beforeEach(async ({ page, baseURL }) => { - await page.goto(`${baseURL}`); + await page.goto(`${baseURL}react/basic`); // Navigate to Formik form tab await page.click('[data-test-id*="formik-form-tab"]'); await page.waitForSelector('[data-test-id^="FormContainer"]', { timeout: 10000 });