Enhance stories for front component in sdk (#17925)

<img width="3838" height="2014" alt="image"
src="https://github.com/user-attachments/assets/87f4d4ba-7b39-4c44-b863-d8bfa6c4fd8a"
/>
This commit is contained in:
Charles Bochet
2026-02-13 17:16:03 +01:00
committed by GitHub
parent 0befb021d0
commit 4817c86bf0
9 changed files with 603 additions and 1 deletions
+1
View File
@@ -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",
@@ -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<void> => {
@@ -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'),
@@ -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 (
<Container data-testid="emotion-component">
<Heading>Emotion Styled Component</Heading>
<Count data-testid="emotion-count">Count: {count}</Count>
<StyledButton
data-testid="emotion-button"
onClick={() => setCount((previous) => previous + 1)}
>
Increment
</StyledButton>
</Container>
);
};
export default defineFrontComponent({
universalIdentifier: 'test-emotion-0000-0000-0000-000000000006',
name: 'emotion-component',
description: 'A front component using @emotion/styled',
component: EmotionComponent,
});
@@ -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 (
<div
data-testid="mui-component"
style={{
padding: 24,
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
}}
>
<h2
style={{
fontSize: 20,
fontWeight: 500,
color: '#1976d2',
marginBottom: 16,
}}
>
Material UI Component
</h2>
<p
data-testid="mui-count"
style={{ fontSize: 32, fontWeight: 700, marginBottom: 16 }}
>
Count: {count}
</p>
<div style={{ display: 'flex', gap: 8 }}>
<Button
data-testid="mui-button"
variant="contained"
onClick={() => setCount((previous) => previous + 1)}
>
Increment
</Button>
<Button variant="outlined" onClick={() => setCount(0)}>
Reset
</Button>
</div>
</div>
);
};
export default defineFrontComponent({
universalIdentifier: 'test-mui00-0000-0000-0000-000000000009',
name: 'mui-component',
description: 'A front component using Material UI',
component: MuiComponent,
});
@@ -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 (
<button
className={`${baseClasses} ${variantClasses[variant]} ${className}`}
{...props}
>
{children}
</button>
);
};
// Simulated shadcn Card component
const Card = ({
children,
className = '',
...props
}: {
children: React.ReactNode;
className?: string;
'data-testid'?: string;
}) => (
<div
className={`rounded-lg border border-input bg-background shadow-sm ${className}`}
{...props}
>
{children}
</div>
);
const ShadcnComponent = () => {
const [count, setCount] = useState(0);
return (
<>
<style>{SHADCN_CSS}</style>
<Card data-testid="shadcn-component">
<div className="flex-col gap-3 p-6">
<h2 className="text-lg font-semibold text-primary">
shadcn UI Component
</h2>
<p className="text-sm text-muted-foreground">
Simulates the shadcn pattern: Tailwind utilities + composable
primitives.
</p>
<div className="bg-muted rounded-md p-4">
<span
data-testid="shadcn-count"
className="text-2xl font-semibold text-primary"
>
Count: {count}
</span>
</div>
<div className="flex gap-2">
<Button
data-testid="shadcn-button"
onClick={() => setCount((previous) => previous + 1)}
>
Increment
</Button>
<Button
variant="outline"
onClick={() => setCount(0)}
>
Reset
</Button>
</div>
</div>
</Card>
</>
);
};
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,
});
@@ -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 (
<Container data-testid="styled-components-component">
<Heading>Styled Components Example</Heading>
<Count data-testid="styled-components-count">Count: {count}</Count>
<StyledButton
data-testid="styled-components-button"
onClick={() => setCount((previous) => previous + 1)}
>
Increment
</StyledButton>
</Container>
);
};
export default defineFrontComponent({
universalIdentifier: 'test-styled-0000-0000-0000-000000000007',
name: 'styled-components-component',
description: 'A front component using styled-components',
component: StyledComponentsComponent,
});
@@ -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 (
<div
data-testid="twenty-ui-component"
style={{
padding: 24,
display: 'flex',
flexDirection: 'column',
gap: 16,
}}
>
<H2Title title="Twenty UI Component" />
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<Tag text={`Count: ${count}`} color="blue" />
<Chip label="Remote Component" variant={ChipVariant.Highlighted} />
</div>
<div style={{ display: 'flex', gap: 8 }}>
<Button
data-testid="twenty-ui-button"
title="Increment"
onClick={() => setCount((previous) => previous + 1)}
/>
<Button title="Reset" variant="secondary" onClick={() => setCount(0)} />
</div>
</div>
);
};
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,
});
+202 -1
View File
@@ -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"