diff --git a/packages/twenty-sdk/package.json b/packages/twenty-sdk/package.json index c4ee5adebbd..e4430e8d62b 100644 --- a/packages/twenty-sdk/package.json +++ b/packages/twenty-sdk/package.json @@ -73,6 +73,7 @@ "zod": "^4.1.11" }, "devDependencies": { + "@mui/material": "^7.3.8", "@prettier/sync": "^0.5.2", "@storybook/addon-vitest": "^10.1.11", "@storybook/react-vite": "^10.1.11", diff --git a/packages/twenty-sdk/scripts/front-component-stories/build-source-examples.ts b/packages/twenty-sdk/scripts/front-component-stories/build-source-examples.ts index a78fe455f86..fd425cb87f1 100644 --- a/packages/twenty-sdk/scripts/front-component-stories/build-source-examples.ts +++ b/packages/twenty-sdk/scripts/front-component-stories/build-source-examples.ts @@ -16,6 +16,11 @@ const STORY_COMPONENTS = [ 'lifecycle.front-component', 'chakra-example.front-component', 'tailwind-example.front-component', + 'emotion-example.front-component', + 'styled-components-example.front-component', + 'shadcn-example.front-component', + 'mui-example.front-component', + 'twenty-ui-example.front-component', ]; export const buildSourceExamples = async (): Promise => { diff --git a/packages/twenty-sdk/src/front-component-renderer/__stories__/FrontComponentRenderer.stories.tsx b/packages/twenty-sdk/src/front-component-renderer/__stories__/FrontComponentRenderer.stories.tsx index d798244bd1f..9492efd1232 100644 --- a/packages/twenty-sdk/src/front-component-renderer/__stories__/FrontComponentRenderer.stories.tsx +++ b/packages/twenty-sdk/src/front-component-renderer/__stories__/FrontComponentRenderer.stories.tsx @@ -110,6 +110,46 @@ export const TailwindExample: Story = { }, }; +export const EmotionExample: Story = { + args: { + componentUrl: getBuiltStoryComponentPathForRender( + 'emotion-example.front-component', + ), + }, +}; + +export const StyledComponentsExample: Story = { + args: { + componentUrl: getBuiltStoryComponentPathForRender( + 'styled-components-example.front-component', + ), + }, +}; + +export const ShadcnExample: Story = { + args: { + componentUrl: getBuiltStoryComponentPathForRender( + 'shadcn-example.front-component', + ), + }, +}; + +export const MuiExample: Story = { + args: { + componentUrl: getBuiltStoryComponentPathForRender( + 'mui-example.front-component', + ), + }, +}; + +export const TwentyUiExample: Story = { + args: { + componentUrl: getBuiltStoryComponentPathForRender( + 'twenty-ui-example.front-component', + ), + }, +}; + export const ErrorHandling: Story = { args: { componentUrl: getBuiltStoryComponentPathForRender('nonexistent.front-component'), diff --git a/packages/twenty-sdk/src/front-component-renderer/__stories__/example-sources/emotion-example.front-component.tsx b/packages/twenty-sdk/src/front-component-renderer/__stories__/example-sources/emotion-example.front-component.tsx new file mode 100644 index 00000000000..33fdbba3787 --- /dev/null +++ b/packages/twenty-sdk/src/front-component-renderer/__stories__/example-sources/emotion-example.front-component.tsx @@ -0,0 +1,59 @@ +import styled from '@emotion/styled'; +import { useState } from 'react'; +import { defineFrontComponent } from '@/sdk'; + +const Container = styled.div` + padding: 20px; + background-color: #fefce8; + border: 2px solid #facc15; + border-radius: 12px; + font-family: system-ui, sans-serif; +`; + +const Heading = styled.h2` + color: #854d0e; + font-weight: 700; + font-size: 18px; + margin-bottom: 12px; +`; + +const Count = styled.p` + font-size: 32px; + font-weight: 800; + color: #ca8a04; + margin-bottom: 16px; +`; + +const StyledButton = styled.button` + padding: 10px 20px; + background-color: #eab308; + color: white; + border: none; + border-radius: 6px; + font-weight: 600; + cursor: pointer; +`; + +const EmotionComponent = () => { + const [count, setCount] = useState(0); + + return ( + + Emotion Styled Component + Count: {count} + setCount((previous) => previous + 1)} + > + Increment + + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'test-emotion-0000-0000-0000-000000000006', + name: 'emotion-component', + description: 'A front component using @emotion/styled', + component: EmotionComponent, +}); diff --git a/packages/twenty-sdk/src/front-component-renderer/__stories__/example-sources/mui-example.front-component.tsx b/packages/twenty-sdk/src/front-component-renderer/__stories__/example-sources/mui-example.front-component.tsx new file mode 100644 index 00000000000..7ad3d0290f6 --- /dev/null +++ b/packages/twenty-sdk/src/front-component-renderer/__stories__/example-sources/mui-example.front-component.tsx @@ -0,0 +1,53 @@ +import Button from '@mui/material/Button'; +import { useState } from 'react'; +import { defineFrontComponent } from '@/sdk'; + +const MuiComponent = () => { + const [count, setCount] = useState(0); + + return ( +
+

+ Material UI Component +

+

+ Count: {count} +

+
+ + +
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'test-mui00-0000-0000-0000-000000000009', + name: 'mui-component', + description: 'A front component using Material UI', + component: MuiComponent, +}); diff --git a/packages/twenty-sdk/src/front-component-renderer/__stories__/example-sources/shadcn-example.front-component.tsx b/packages/twenty-sdk/src/front-component-renderer/__stories__/example-sources/shadcn-example.front-component.tsx new file mode 100644 index 00000000000..2d914b135cb --- /dev/null +++ b/packages/twenty-sdk/src/front-component-renderer/__stories__/example-sources/shadcn-example.front-component.tsx @@ -0,0 +1,144 @@ +import { useState } from 'react'; +import { defineFrontComponent } from '@/sdk'; + +// shadcn UI outputs pre-built components that use Tailwind utility classes +// with Radix UI primitives underneath. This example simulates that pattern: +// className-based styling with a bundled CSS subset. +const SHADCN_CSS = ` +*,*::before,*::after{box-sizing:border-box;margin:0;padding:0} +.inline-flex{display:inline-flex} +.flex{display:flex} +.flex-col{display:flex;flex-direction:column} +.items-center{align-items:center} +.justify-center{justify-content:center} +.gap-2{gap:.5rem} +.gap-3{gap:.75rem} +.rounded-md{border-radius:.375rem} +.rounded-lg{border-radius:.5rem} +.border{border-width:1px} +.border-input{border-color:#e2e8f0} +.bg-background{background-color:#fff} +.bg-primary{background-color:#0f172a} +.bg-muted{background-color:#f1f5f9} +.bg-destructive{background-color:#ef4444} +.p-4{padding:1rem} +.p-6{padding:1.5rem} +.px-4{padding-left:1rem;padding-right:1rem} +.py-2{padding-top:.5rem;padding-bottom:.5rem} +.h-10{height:2.5rem} +.w-full{width:100%} +.text-sm{font-size:.875rem;line-height:1.25rem} +.text-lg{font-size:1.125rem;line-height:1.75rem} +.text-2xl{font-size:1.5rem;line-height:2rem} +.font-medium{font-weight:500} +.font-semibold{font-weight:600} +.text-primary{color:#0f172a} +.text-primary-foreground{color:#fff} +.text-muted-foreground{color:#64748b} +.text-destructive-foreground{color:#fff} +.shadow-sm{box-shadow:0 1px 2px 0 rgb(0 0 0/.05)} +.cursor-pointer{cursor:pointer} +.ring-offset-background{--ring-offset:0px} +.transition-colors{transition:color .15s,background-color .15s,border-color .15s} +`; + +// Simulated shadcn Button component (normally generated by shadcn CLI) +const Button = ({ + children, + variant = 'default', + className = '', + ...props +}: { + children: React.ReactNode; + variant?: 'default' | 'destructive' | 'outline'; + className?: string; + 'data-testid'?: string; + onClick?: () => void; +}) => { + const baseClasses = + 'inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 cursor-pointer transition-colors'; + + const variantClasses = { + default: 'bg-primary text-primary-foreground', + destructive: 'bg-destructive text-destructive-foreground', + outline: 'border border-input bg-background', + }; + + return ( + + ); +}; + +// Simulated shadcn Card component +const Card = ({ + children, + className = '', + ...props +}: { + children: React.ReactNode; + className?: string; + 'data-testid'?: string; +}) => ( +
+ {children} +
+); + +const ShadcnComponent = () => { + const [count, setCount] = useState(0); + + return ( + <> + + +
+

+ shadcn UI Component +

+

+ Simulates the shadcn pattern: Tailwind utilities + composable + primitives. +

+
+ + Count: {count} + +
+
+ + +
+
+
+ + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'test-shadcn-0000-0000-0000-000000000008', + name: 'shadcn-component', + description: + 'A front component simulating the shadcn UI pattern (Tailwind + composable primitives)', + component: ShadcnComponent, +}); diff --git a/packages/twenty-sdk/src/front-component-renderer/__stories__/example-sources/styled-components-example.front-component.tsx b/packages/twenty-sdk/src/front-component-renderer/__stories__/example-sources/styled-components-example.front-component.tsx new file mode 100644 index 00000000000..67e406db649 --- /dev/null +++ b/packages/twenty-sdk/src/front-component-renderer/__stories__/example-sources/styled-components-example.front-component.tsx @@ -0,0 +1,59 @@ +import { useState } from 'react'; +import styled from 'styled-components'; +import { defineFrontComponent } from '@/sdk'; + +const Container = styled.div` + padding: 20px; + background-color: #fdf2f8; + border: 2px solid #ec4899; + border-radius: 12px; + font-family: system-ui, sans-serif; +`; + +const Heading = styled.h2` + color: #9d174d; + font-weight: 700; + font-size: 18px; + margin-bottom: 12px; +`; + +const Count = styled.p` + font-size: 32px; + font-weight: 800; + color: #db2777; + margin-bottom: 16px; +`; + +const StyledButton = styled.button` + padding: 10px 20px; + background-color: #ec4899; + color: white; + border: none; + border-radius: 6px; + font-weight: 600; + cursor: pointer; +`; + +const StyledComponentsComponent = () => { + const [count, setCount] = useState(0); + + return ( + + Styled Components Example + Count: {count} + setCount((previous) => previous + 1)} + > + Increment + + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'test-styled-0000-0000-0000-000000000007', + name: 'styled-components-component', + description: 'A front component using styled-components', + component: StyledComponentsComponent, +}); diff --git a/packages/twenty-sdk/src/front-component-renderer/__stories__/example-sources/twenty-ui-example.front-component.tsx b/packages/twenty-sdk/src/front-component-renderer/__stories__/example-sources/twenty-ui-example.front-component.tsx new file mode 100644 index 00000000000..356c3df8a10 --- /dev/null +++ b/packages/twenty-sdk/src/front-component-renderer/__stories__/example-sources/twenty-ui-example.front-component.tsx @@ -0,0 +1,40 @@ +import { useState } from 'react'; +import { defineFrontComponent } from '@/sdk'; +import { Button, Chip, ChipVariant, H2Title, Tag } from 'twenty-sdk/ui'; + +const TwentyUiComponent = () => { + const [count, setCount] = useState(0); + + return ( +
+ +
+ + +
+
+
+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'test-20ui0-0000-0000-0000-000000000010', + name: 'twenty-ui-component', + description: 'A front component using Twenty UI remote components', + component: TwentyUiComponent, +}); diff --git a/yarn.lock b/yarn.lock index fe335e624fd..6b8549de899 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3489,6 +3489,13 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.28.6, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.7": + version: 7.28.6 + resolution: "@babel/runtime@npm:7.28.6" + checksum: 10c0/358cf2429992ac1c466df1a21c1601d595c46930a13c1d4662fde908d44ee78ec3c183aaff513ecb01ef8c55c3624afe0309eeeb34715672dbfadb7feedb2c0d + languageName: node + linkType: hard + "@babel/template@npm:^7.18.10, @babel/template@npm:^7.20.7, @babel/template@npm:^7.22.15, @babel/template@npm:^7.22.5, @babel/template@npm:^7.25.9, @babel/template@npm:^7.27.2, @babel/template@npm:^7.3.3": version: 7.27.2 resolution: "@babel/template@npm:7.27.2" @@ -11291,6 +11298,151 @@ __metadata: languageName: node linkType: hard +"@mui/core-downloads-tracker@npm:^7.3.8": + version: 7.3.8 + resolution: "@mui/core-downloads-tracker@npm:7.3.8" + checksum: 10c0/bbce4375e47b8ef46af762d8837e006ccca26cc56c0efb34ac497a090e7e9fff868ea2dd423d3c28c4cf51ff9e4fbbb024d3afe1d19849533b06e6924fcfc4e9 + languageName: node + linkType: hard + +"@mui/material@npm:^7.3.8": + version: 7.3.8 + resolution: "@mui/material@npm:7.3.8" + dependencies: + "@babel/runtime": "npm:^7.28.6" + "@mui/core-downloads-tracker": "npm:^7.3.8" + "@mui/system": "npm:^7.3.8" + "@mui/types": "npm:^7.4.11" + "@mui/utils": "npm:^7.3.8" + "@popperjs/core": "npm:^2.11.8" + "@types/react-transition-group": "npm:^4.4.12" + clsx: "npm:^2.1.1" + csstype: "npm:^3.2.3" + prop-types: "npm:^15.8.1" + react-is: "npm:^19.2.3" + react-transition-group: "npm:^4.4.5" + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@mui/material-pigment-css": ^7.3.8 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@mui/material-pigment-css": + optional: true + "@types/react": + optional: true + checksum: 10c0/065b97627cfdb7dd6237ff4437639c5d8df610e844bfa6f8c84e647d1f7bb0157cfd7b1938cf371d2b281d8bf85580c8b405638d741aae797ff64ee3b98943fc + languageName: node + linkType: hard + +"@mui/private-theming@npm:^7.3.8": + version: 7.3.8 + resolution: "@mui/private-theming@npm:7.3.8" + dependencies: + "@babel/runtime": "npm:^7.28.6" + "@mui/utils": "npm:^7.3.8" + prop-types: "npm:^15.8.1" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/4e7f1528dcaf00a036ee03a430569d66fe66313c2e11d6d70df705a54cb15e88af00e69ef55de2182ea86966c66d9f9f7e4d389059a8213901159fb03ecd679d + languageName: node + linkType: hard + +"@mui/styled-engine@npm:^7.3.8": + version: 7.3.8 + resolution: "@mui/styled-engine@npm:7.3.8" + dependencies: + "@babel/runtime": "npm:^7.28.6" + "@emotion/cache": "npm:^11.14.0" + "@emotion/serialize": "npm:^1.3.3" + "@emotion/sheet": "npm:^1.4.0" + csstype: "npm:^3.2.3" + prop-types: "npm:^15.8.1" + peerDependencies: + "@emotion/react": ^11.4.1 + "@emotion/styled": ^11.3.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + checksum: 10c0/44defb63e5c489943c36c6b0a8551093389a65429c9c2cf21fe5f3fb338962a45f5fd57300a64c965de43be755424b65aef762944a55e2de862018d99b82f6a8 + languageName: node + linkType: hard + +"@mui/system@npm:^7.3.8": + version: 7.3.8 + resolution: "@mui/system@npm:7.3.8" + dependencies: + "@babel/runtime": "npm:^7.28.6" + "@mui/private-theming": "npm:^7.3.8" + "@mui/styled-engine": "npm:^7.3.8" + "@mui/types": "npm:^7.4.11" + "@mui/utils": "npm:^7.3.8" + clsx: "npm:^2.1.1" + csstype: "npm:^3.2.3" + prop-types: "npm:^15.8.1" + peerDependencies: + "@emotion/react": ^11.5.0 + "@emotion/styled": ^11.3.0 + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@emotion/react": + optional: true + "@emotion/styled": + optional: true + "@types/react": + optional: true + checksum: 10c0/e584af421b553c1ab11783a769c26ca38f2415d71ffd0fc7296e299e47ab0650d35c6be408f8486239b1eab78481af8a663615c758f96bc1e8290482afcc0c22 + languageName: node + linkType: hard + +"@mui/types@npm:^7.4.11": + version: 7.4.11 + resolution: "@mui/types@npm:7.4.11" + dependencies: + "@babel/runtime": "npm:^7.28.6" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/c7e73d3fbcd3d8fa55ef172faa58d5b8aa64d1faf26b2f4a13871444dd0e57577567c9eefc4a14de0bc0644f7c325302942496a41b165007defed24548e7da04 + languageName: node + linkType: hard + +"@mui/utils@npm:^7.3.8": + version: 7.3.8 + resolution: "@mui/utils@npm:7.3.8" + dependencies: + "@babel/runtime": "npm:^7.28.6" + "@mui/types": "npm:^7.4.11" + "@types/prop-types": "npm:^15.7.15" + clsx: "npm:^2.1.1" + prop-types: "npm:^15.8.1" + react-is: "npm:^19.2.3" + peerDependencies: + "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10c0/369e53295f8cd75d7ea3ef8d1c532cae6a34b7e7d834b2333933f52db04bf2aa3b8a808bdb864ed69b6cc56b1395337e93952e0cd06da872dfd867f1bb53dd34 + languageName: node + linkType: hard + "@n1ru4l/push-pull-async-iterable-iterator@npm:^3.1.0": version: 3.2.0 resolution: "@n1ru4l/push-pull-async-iterable-iterator@npm:3.2.0" @@ -15844,7 +15996,7 @@ __metadata: languageName: node linkType: hard -"@popperjs/core@npm:^2.9.0": +"@popperjs/core@npm:^2.11.8, @popperjs/core@npm:^2.9.0": version: 2.11.8 resolution: "@popperjs/core@npm:2.11.8" checksum: 10c0/4681e682abc006d25eb380d0cf3efc7557043f53b6aea7a5057d0d1e7df849a00e281cd8ea79c902a35a414d7919621fc2ba293ecec05f413598e0b23d5a1e63 @@ -24347,6 +24499,13 @@ __metadata: languageName: node linkType: hard +"@types/prop-types@npm:^15.7.15": + version: 15.7.15 + resolution: "@types/prop-types@npm:15.7.15" + checksum: 10c0/b59aad1ad19bf1733cf524fd4e618196c6c7690f48ee70a327eb450a42aab8e8a063fbe59ca0a5701aebe2d92d582292c0fb845ea57474f6a15f6994b0e260b2 + languageName: node + linkType: hard + "@types/psl@npm:^1.1.3": version: 1.1.3 resolution: "@types/psl@npm:1.1.3" @@ -24415,6 +24574,15 @@ __metadata: languageName: node linkType: hard +"@types/react-transition-group@npm:^4.4.12": + version: 4.4.12 + resolution: "@types/react-transition-group@npm:4.4.12" + peerDependencies: + "@types/react": "*" + checksum: 10c0/0441b8b47c69312c89ec0760ba477ba1a0808a10ceef8dc1c64b1013ed78517332c30f18681b0ec0b53542731f1ed015169fed1d127cc91222638ed955478ec7 + languageName: node + linkType: hard + "@types/react@npm:*, @types/react@npm:>=16, @types/react@npm:^19, @types/react@npm:^19.0.8": version: 19.1.0 resolution: "@types/react@npm:19.1.0" @@ -33745,6 +33913,16 @@ __metadata: languageName: node linkType: hard +"dom-helpers@npm:^5.0.1": + version: 5.2.1 + resolution: "dom-helpers@npm:5.2.1" + dependencies: + "@babel/runtime": "npm:^7.8.7" + csstype: "npm:^3.0.2" + checksum: 10c0/f735074d66dd759b36b158fa26e9d00c9388ee0e8c9b16af941c38f014a37fc80782de83afefd621681b19ac0501034b4f1c4a3bff5caa1b8667f0212b5e124c + languageName: node + linkType: hard + "dom-serializer@npm:0": version: 0.2.2 resolution: "dom-serializer@npm:0.2.2" @@ -52143,6 +52321,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^19.2.3": + version: 19.2.4 + resolution: "react-is@npm:19.2.4" + checksum: 10c0/477a7cfc900f24194606e315fa353856a3a13487ea8eca841678817cad4daef64339ea0d1e84e58459fc75dbe0d9ba00bb0cc626db3d07e0cf31edc64cb4fa37 + languageName: node + linkType: hard + "react-loading-skeleton@npm:^3.3.1": version: 3.5.0 resolution: "react-loading-skeleton@npm:3.5.0" @@ -52474,6 +52659,21 @@ __metadata: languageName: node linkType: hard +"react-transition-group@npm:^4.4.5": + version: 4.4.5 + resolution: "react-transition-group@npm:4.4.5" + dependencies: + "@babel/runtime": "npm:^7.5.5" + dom-helpers: "npm:^5.0.1" + loose-envify: "npm:^1.4.0" + prop-types: "npm:^15.6.2" + peerDependencies: + react: ">=16.6.0" + react-dom: ">=16.6.0" + checksum: 10c0/2ba754ba748faefa15f87c96dfa700d5525054a0141de8c75763aae6734af0740e77e11261a1e8f4ffc08fd9ab78510122e05c21c2d79066c38bb6861a886c82 + languageName: node + linkType: hard + "react-virtualized-auto-sizer@npm:^1.0.26": version: 1.0.26 resolution: "react-virtualized-auto-sizer@npm:1.0.26" @@ -58131,6 +58331,7 @@ __metadata: "@chakra-ui/react": "npm:^3.33.0" "@emotion/react": "npm:^11.14.0" "@genql/cli": "npm:^3.0.3" + "@mui/material": "npm:^7.3.8" "@prettier/sync": "npm:^0.5.2" "@quilted/threads": "npm:^4.0.1" "@remote-dom/core": "npm:^1.10.1"