mirror of
https://github.com/twentyhq/twenty
synced 2026-04-21 13:37:22 +00:00
Remote dom twenty UI POC (#17652)
Create a proof of concept which allows us to use twenty-ui in the remote-dom. For now only the button is allowed. https://github.com/user-attachments/assets/e5441d2c-eb63-4b99-931b-86ee14621393 Known limitation: The icons are not yet available
This commit is contained in:
parent
5c91a96e84
commit
66d7182d02
15 changed files with 230 additions and 21 deletions
|
|
@ -1,12 +1,10 @@
|
|||
import { useState } from 'react';
|
||||
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { getMockFrontComponentUrl } from '@/front-components/utils/mockFrontComponent';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useState } from 'react';
|
||||
import { FrontComponentRenderer as SharedFrontComponentRenderer } from 'twenty-sdk/front-component';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
type FrontComponentRendererProps = {
|
||||
frontComponentId: string;
|
||||
};
|
||||
|
|
@ -14,6 +12,7 @@ type FrontComponentRendererProps = {
|
|||
export const FrontComponentRenderer = ({
|
||||
frontComponentId: _frontComponentId,
|
||||
}: FrontComponentRendererProps) => {
|
||||
const theme = useTheme();
|
||||
const [hasError, setHasError] = useState(false);
|
||||
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
|
@ -35,6 +34,7 @@ export const FrontComponentRenderer = ({
|
|||
|
||||
return (
|
||||
<SharedFrontComponentRenderer
|
||||
theme={theme}
|
||||
componentUrl={getMockFrontComponentUrl()}
|
||||
onError={handleError}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,18 @@
|
|||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
// eslint-disable-next-line
|
||||
const mockFrontComponentCode = `
|
||||
var r=globalThis.React.useState,s=globalThis.React.useEffect;var n=globalThis.jsx,o=globalThis.jsxs,g=globalThis.React.Fragment;var e=globalThis.RemoteComponents,x=()=>{let[a,l]=r(0),[m,p]=r(0);return s(()=>{let t=setInterval(()=>{p(i=>i+1)},1e3);return()=>clearInterval(t)},[]),o(e.HtmlDiv,{style:{padding:"20px",fontFamily:"Arial, sans-serif",maxWidth:"400px",margin:"0 auto",display:"flex",flexDirection:"column",alignItems:"center"},children:[n(e.HtmlH1,{style:{color:"#333",marginBottom:"20px",fontSize:"24px"},children:"Test Component"}),o(e.HtmlP,{style:{fontSize:"18px",marginBottom:"10px",color:"#666"},children:["Count: ",a]}),o(e.HtmlP,{style:{fontSize:"18px",marginBottom:"20px",color:"#666"},children:["Timer: ",m,"s"]}),n(e.HtmlButton,{onClick:()=>l(a+1),style:{padding:"10px 20px",fontSize:"16px",backgroundColor:"#007bff",color:"white",border:"none",borderRadius:"5px",cursor:"pointer",transition:"all 0.3s ease",boxShadow:"0 2px 4px rgba(0, 0, 0, 0.2)"},onMouseEnter:t=>{t.currentTarget.style.backgroundColor="#0056b3",t.currentTarget.style.transform="translateY(-2px)",t.currentTarget.style.boxShadow="0 4px 8px rgba(0, 0, 0, 0.3)"},onMouseLeave:t=>{t.currentTarget.style.backgroundColor="#007bff",t.currentTarget.style.transform="translateY(0)",t.currentTarget.style.boxShadow="0 2px 4px rgba(0, 0, 0, 0.2)"},children:"Increment"})]})},b=globalThis.jsx(x,{});export{b as default};
|
||||
const { jsx } = globalThis;
|
||||
const { TwentyUiButton } = globalThis.RemoteComponents;
|
||||
|
||||
export default jsx(TwentyUiButton, {
|
||||
variant: 'primary',
|
||||
disabled: false,
|
||||
fullWidth: false,
|
||||
onClick: () => {
|
||||
console.log('Button clicked');
|
||||
},
|
||||
title: 'Click me',
|
||||
});
|
||||
`;
|
||||
|
||||
let cachedMockBlobUrl: string | null = null;
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@
|
|||
"jsonc-parser": "^3.2.0",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"react": "^18.0.0",
|
||||
"twenty-ui": "workspace:*",
|
||||
"typescript": "^5.9.2",
|
||||
"uuid": "^13.0.0",
|
||||
"vite": "^7.0.0",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
/* eslint-disable no-console */
|
||||
import * as prettier from '@prettier/sync';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { IndentationText, Project, QuoteKind } from 'ts-morph';
|
||||
|
||||
import { ALLOWED_HTML_ELEMENTS } from '../../src/front-component-constants/AllowedHtmlElements';
|
||||
import { ALLOWED_UI_COMPONENTS } from '../../src/front-component-constants/AllowedUiComponents';
|
||||
import { COMMON_HTML_EVENTS } from '../../src/front-component-constants/CommonHtmlEvents';
|
||||
import { EVENT_TO_REACT } from '../../src/front-component-constants/EventToReact';
|
||||
import { HTML_COMMON_PROPERTIES } from '../../src/front-component-constants/HtmlCommonProperties';
|
||||
|
|
@ -17,6 +17,7 @@ import {
|
|||
generateRemoteElements,
|
||||
HtmlElementConfigArrayZ,
|
||||
OUTPUT_FILES,
|
||||
UiComponentConfigArrayZ,
|
||||
} from './generators';
|
||||
|
||||
const SCRIPT_DIR = path.dirname(new URL(import.meta.url).pathname);
|
||||
|
|
@ -59,6 +60,28 @@ const getHtmlElementSchemas = (): ComponentSchema[] => {
|
|||
}));
|
||||
};
|
||||
|
||||
const getUiComponentSchemas = (): ComponentSchema[] => {
|
||||
const result = UiComponentConfigArrayZ.safeParse(ALLOWED_UI_COMPONENTS);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(
|
||||
`Invalid UI component configuration:\n${formatZodError(result.error)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return result.data.map((component) => ({
|
||||
name: component.name,
|
||||
tagName: component.name,
|
||||
customElementName: component.tag,
|
||||
properties: component.properties,
|
||||
events: COMMON_HTML_EVENTS,
|
||||
isHtmlElement: false,
|
||||
htmlTag: undefined,
|
||||
componentImport: component.componentImport,
|
||||
componentPath: component.componentPath,
|
||||
}));
|
||||
};
|
||||
|
||||
const createProject = (): Project => {
|
||||
return new Project({
|
||||
manipulationSettings: {
|
||||
|
|
@ -99,8 +122,11 @@ const main = (): void => {
|
|||
console.log('📖 Generating remote DOM elements...\n');
|
||||
|
||||
let htmlElements: ComponentSchema[];
|
||||
let uiComponents: ComponentSchema[];
|
||||
|
||||
try {
|
||||
htmlElements = getHtmlElementSchemas();
|
||||
uiComponents = getUiComponentSchemas();
|
||||
} catch (error) {
|
||||
console.error('❌ Validation failed:', error);
|
||||
process.exit(1);
|
||||
|
|
@ -113,8 +139,15 @@ const main = (): void => {
|
|||
console.log(
|
||||
` Events: ${COMMON_HTML_EVENTS.length} common events per element`,
|
||||
);
|
||||
|
||||
console.log(`\nUI Components: ${uiComponents.length} components`);
|
||||
console.log(
|
||||
` Tags: ${uiComponents.map((component) => component.customElementName).join(', ')}`,
|
||||
);
|
||||
console.log('');
|
||||
|
||||
const allComponents = [...htmlElements, ...uiComponents];
|
||||
|
||||
ensureDirectoriesExist();
|
||||
|
||||
const project = createProject();
|
||||
|
|
@ -122,7 +155,7 @@ const main = (): void => {
|
|||
console.log('Host files:');
|
||||
const hostRegistry = generateHostRegistry(
|
||||
project,
|
||||
htmlElements,
|
||||
allComponents,
|
||||
EVENT_TO_REACT,
|
||||
);
|
||||
writeGeneratedFile(
|
||||
|
|
@ -134,7 +167,7 @@ const main = (): void => {
|
|||
console.log('\nRemote files:');
|
||||
const remoteElements = generateRemoteElements(
|
||||
project,
|
||||
htmlElements,
|
||||
allComponents,
|
||||
HTML_COMMON_PROPERTIES,
|
||||
COMMON_HTML_EVENTS,
|
||||
);
|
||||
|
|
@ -144,7 +177,7 @@ const main = (): void => {
|
|||
remoteElements.getFullText(),
|
||||
);
|
||||
|
||||
const remoteComponents = generateRemoteComponents(project, htmlElements);
|
||||
const remoteComponents = generateRemoteComponents(project, allComponents);
|
||||
writeGeneratedFile(
|
||||
REMOTE_GENERATED_DIR,
|
||||
OUTPUT_FILES.REMOTE_COMPONENTS,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { Project, SourceFile } from 'ts-morph';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { CUSTOM_ELEMENT_NAMES } from './constants';
|
||||
import { type ComponentSchema } from './schemas';
|
||||
import { addFileHeader, addStatement } from './utils';
|
||||
|
|
@ -65,12 +66,25 @@ const filterProps = (props: Record<string, unknown>) => {
|
|||
};`;
|
||||
};
|
||||
|
||||
const generateWrapperComponent = (component: ComponentSchema): string => {
|
||||
const generateHtmlWrapperComponent = (component: ComponentSchema): string => {
|
||||
return `const ${component.name}Wrapper = ({ children, ...props }: { children?: React.ReactNode } & Record<string, unknown>) => {
|
||||
return React.createElement('${component.htmlTag}', filterProps(props), children);
|
||||
};`;
|
||||
};
|
||||
|
||||
const generateUiWrapperComponent = (component: ComponentSchema): string => {
|
||||
return `const ${component.name}Wrapper = ({ children, ...props }: { children?: React.ReactNode } & Record<string, unknown>) => {
|
||||
return React.createElement(${component.componentImport}, filterProps(props), children);
|
||||
};`;
|
||||
};
|
||||
|
||||
const generateWrapperComponent = (component: ComponentSchema): string => {
|
||||
if (component.isHtmlElement) {
|
||||
return generateHtmlWrapperComponent(component);
|
||||
}
|
||||
return generateUiWrapperComponent(component);
|
||||
};
|
||||
|
||||
const generateRegistryMap = (components: ComponentSchema[]): string => {
|
||||
const entries = components
|
||||
.map(
|
||||
|
|
@ -89,6 +103,28 @@ ${entries}
|
|||
]);`;
|
||||
};
|
||||
|
||||
const groupImportsByPath = (
|
||||
components: ComponentSchema[],
|
||||
): Map<string, string[]> => {
|
||||
const importsByPath = new Map<string, string[]>();
|
||||
|
||||
for (const component of components) {
|
||||
if (
|
||||
!component.isHtmlElement &&
|
||||
isDefined(component.componentPath) &&
|
||||
isDefined(component.componentImport)
|
||||
) {
|
||||
const existing = importsByPath.get(component.componentPath) ?? [];
|
||||
if (!existing.includes(component.componentImport)) {
|
||||
existing.push(component.componentImport);
|
||||
}
|
||||
importsByPath.set(component.componentPath, existing);
|
||||
}
|
||||
}
|
||||
|
||||
return importsByPath;
|
||||
};
|
||||
|
||||
export const generateHostRegistry = (
|
||||
project: Project,
|
||||
components: ComponentSchema[],
|
||||
|
|
@ -110,6 +146,15 @@ export const generateHostRegistry = (
|
|||
namedImports: ['RemoteFragmentRenderer', 'createRemoteComponentRenderer'],
|
||||
});
|
||||
|
||||
const uiImports = groupImportsByPath(components);
|
||||
|
||||
for (const [modulePath, namedImports] of uiImports) {
|
||||
sourceFile.addImportDeclaration({
|
||||
moduleSpecifier: modulePath,
|
||||
namedImports,
|
||||
});
|
||||
}
|
||||
|
||||
addStatement(sourceFile, generateRuntimeUtilities(eventToReactMapping));
|
||||
|
||||
for (const component of components) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,20 @@ export const HtmlElementConfigZ = z.object({
|
|||
|
||||
export const HtmlElementConfigArrayZ = z.array(HtmlElementConfigZ);
|
||||
|
||||
export const UiComponentConfigZ = z.object({
|
||||
tag: z
|
||||
.string()
|
||||
.regex(/^twenty-ui-[a-z0-9-]+$/, 'Tag must start with "twenty-ui-"'),
|
||||
name: z
|
||||
.string()
|
||||
.regex(/^TwentyUi[A-Z]/, 'Name must be PascalCase starting with TwentyUi'),
|
||||
properties: z.record(z.string(), PropertySchemaZ),
|
||||
componentImport: z.string().min(1),
|
||||
componentPath: z.string().min(1),
|
||||
});
|
||||
|
||||
export const UiComponentConfigArrayZ = z.array(UiComponentConfigZ);
|
||||
|
||||
export const ComponentSchemaZ = z.object({
|
||||
name: z.string().min(1),
|
||||
tagName: z.string().min(1),
|
||||
|
|
@ -22,9 +36,12 @@ export const ComponentSchemaZ = z.object({
|
|||
properties: z.record(z.string(), PropertySchemaZ),
|
||||
events: z.array(z.string()).readonly(),
|
||||
isHtmlElement: z.boolean(),
|
||||
htmlTag: z.string().min(1),
|
||||
htmlTag: z.string().optional(),
|
||||
componentImport: z.string().optional(),
|
||||
componentPath: z.string().optional(),
|
||||
});
|
||||
|
||||
export type PropertySchema = z.infer<typeof PropertySchemaZ>;
|
||||
export type HtmlElementConfig = z.infer<typeof HtmlElementConfigZ>;
|
||||
export type UiComponentConfig = z.infer<typeof UiComponentConfigZ>;
|
||||
export type ComponentSchema = z.infer<typeof ComponentSchemaZ>;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import { type PropertySchema } from '../front-component/types/PropertySchema';
|
||||
|
||||
export type AllowedUiComponent = {
|
||||
tag: string;
|
||||
name: string;
|
||||
properties: Record<string, PropertySchema>;
|
||||
componentImport: string;
|
||||
componentPath: string;
|
||||
};
|
||||
|
||||
export const ALLOWED_UI_COMPONENTS: AllowedUiComponent[] = [
|
||||
{
|
||||
tag: 'twenty-ui-button',
|
||||
name: 'TwentyUiButton',
|
||||
properties: {
|
||||
title: { type: 'string', optional: true },
|
||||
variant: { type: 'string', optional: true },
|
||||
accent: { type: 'string', optional: true },
|
||||
size: { type: 'string', optional: true },
|
||||
disabled: { type: 'boolean', optional: true },
|
||||
fullWidth: { type: 'boolean', optional: true },
|
||||
},
|
||||
componentImport: 'Button',
|
||||
componentPath: 'twenty-ui/input',
|
||||
},
|
||||
];
|
||||
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
export type { AllowedHtmlElement } from './AllowedHtmlElements';
|
||||
export { ALLOWED_HTML_ELEMENTS } from './AllowedHtmlElements';
|
||||
export type { AllowedUiComponent } from './AllowedUiComponents';
|
||||
export { ALLOWED_UI_COMPONENTS } from './AllowedUiComponents';
|
||||
export { COMMON_HTML_EVENTS } from './CommonHtmlEvents';
|
||||
export { EVENT_TO_REACT } from './EventToReact';
|
||||
export { HTML_COMMON_PROPERTIES } from './HtmlCommonProperties';
|
||||
|
|
|
|||
|
|
@ -2,19 +2,24 @@ import {
|
|||
type RemoteReceiver,
|
||||
RemoteRootRenderer,
|
||||
} from '@remote-dom/react/host';
|
||||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
import { type ThemeType } from 'twenty-ui/theme';
|
||||
import { FrontComponentWorkerEffect } from '../../remote/components/FrontComponentWorkerEffect';
|
||||
import { componentRegistry } from '../generated/host-component-registry';
|
||||
|
||||
type FrontComponentContentProps = {
|
||||
componentUrl: string;
|
||||
onError: (error?: Error) => void;
|
||||
theme: ThemeType;
|
||||
};
|
||||
|
||||
export const FrontComponentRenderer = ({
|
||||
componentUrl,
|
||||
onError,
|
||||
theme,
|
||||
}: FrontComponentContentProps) => {
|
||||
const [receiver, setReceiver] = useState<RemoteReceiver | null>(null);
|
||||
|
||||
|
|
@ -27,10 +32,12 @@ export const FrontComponentRenderer = ({
|
|||
/>
|
||||
|
||||
{isDefined(receiver) && (
|
||||
<RemoteRootRenderer
|
||||
receiver={receiver}
|
||||
components={componentRegistry}
|
||||
/>
|
||||
<ThemeProvider theme={theme}>
|
||||
<RemoteRootRenderer
|
||||
receiver={receiver}
|
||||
components={componentRegistry}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
RemoteFragmentRenderer,
|
||||
createRemoteComponentRenderer,
|
||||
} from '@remote-dom/react/host';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
const INTERNAL_PROPS = new Set(['element', 'receiver', 'components']);
|
||||
|
||||
const EVENT_NAME_MAP: Record<string, string> = {
|
||||
|
|
@ -345,6 +346,12 @@ const HtmlHrWrapper = ({
|
|||
}: { children?: React.ReactNode } & Record<string, unknown>) => {
|
||||
return React.createElement('hr', filterProps(props), children);
|
||||
};
|
||||
const TwentyUiButtonWrapper = ({
|
||||
children,
|
||||
...props
|
||||
}: { children?: React.ReactNode } & Record<string, unknown>) => {
|
||||
return React.createElement(Button, filterProps(props), children);
|
||||
};
|
||||
type ComponentRegistryValue =
|
||||
| ReturnType<typeof createRemoteComponentRenderer>
|
||||
| typeof RemoteFragmentRenderer;
|
||||
|
|
@ -393,5 +400,6 @@ export const componentRegistry: Map<string, ComponentRegistryValue> = new Map([
|
|||
['html-td', createRemoteComponentRenderer(HtmlTdWrapper)],
|
||||
['html-br', createRemoteComponentRenderer(HtmlBrWrapper)],
|
||||
['html-hr', createRemoteComponentRenderer(HtmlHrWrapper)],
|
||||
['twenty-ui-button', createRemoteComponentRenderer(TwentyUiButtonWrapper)],
|
||||
['remote-fragment', RemoteFragmentRenderer],
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ export {
|
|||
HtmlTd,
|
||||
HtmlBr,
|
||||
HtmlHr,
|
||||
TwentyUiButton,
|
||||
} from './remote/generated/remote-components';
|
||||
export type {
|
||||
HtmlCommonProperties,
|
||||
|
|
@ -69,6 +70,7 @@ export type {
|
|||
HtmlButtonProperties,
|
||||
HtmlThProperties,
|
||||
HtmlTdProperties,
|
||||
TwentyUiButtonProperties,
|
||||
} from './remote/generated/remote-elements';
|
||||
export {
|
||||
HtmlDivElement,
|
||||
|
|
@ -114,6 +116,7 @@ export {
|
|||
HtmlTdElement,
|
||||
HtmlBrElement,
|
||||
HtmlHrElement,
|
||||
TwentyUiButtonElement,
|
||||
RemoteRootElement,
|
||||
RemoteFragmentElement,
|
||||
} from './remote/generated/remote-elements';
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ import {
|
|||
HtmlTdElement,
|
||||
HtmlBrElement,
|
||||
HtmlHrElement,
|
||||
TwentyUiButtonElement,
|
||||
} from './remote-elements';
|
||||
|
||||
export const HtmlDiv = createRemoteComponent('html-div', HtmlDivElement, {
|
||||
|
|
@ -1126,3 +1127,31 @@ export const HtmlHr = createRemoteComponent('html-hr', HtmlHrElement, {
|
|||
onDrag: { event: 'drag' },
|
||||
},
|
||||
});
|
||||
export const TwentyUiButton = createRemoteComponent(
|
||||
'twenty-ui-button',
|
||||
TwentyUiButtonElement,
|
||||
{
|
||||
eventProps: {
|
||||
onClick: { event: 'click' },
|
||||
onDblclick: { event: 'dblclick' },
|
||||
onMousedown: { event: 'mousedown' },
|
||||
onMouseup: { event: 'mouseup' },
|
||||
onMouseover: { event: 'mouseover' },
|
||||
onMouseout: { event: 'mouseout' },
|
||||
onMouseenter: { event: 'mouseenter' },
|
||||
onMouseleave: { event: 'mouseleave' },
|
||||
onKeydown: { event: 'keydown' },
|
||||
onKeyup: { event: 'keyup' },
|
||||
onKeypress: { event: 'keypress' },
|
||||
onFocus: { event: 'focus' },
|
||||
onBlur: { event: 'blur' },
|
||||
onChange: { event: 'change' },
|
||||
onInput: { event: 'input' },
|
||||
onSubmit: { event: 'submit' },
|
||||
onScroll: { event: 'scroll' },
|
||||
onWheel: { event: 'wheel' },
|
||||
onContextmenu: { event: 'contextmenu' },
|
||||
onDrag: { event: 'drag' },
|
||||
},
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -608,6 +608,31 @@ export const HtmlHrElement = createRemoteElement<
|
|||
properties: HTML_COMMON_PROPERTIES_CONFIG,
|
||||
events: [...HTML_COMMON_EVENTS_ARRAY],
|
||||
});
|
||||
|
||||
export type TwentyUiButtonProperties = HtmlCommonProperties & {
|
||||
variant?: string;
|
||||
accent?: string;
|
||||
size?: string;
|
||||
disabled?: boolean;
|
||||
fullWidth?: boolean;
|
||||
};
|
||||
|
||||
export const TwentyUiButtonElement = createRemoteElement<
|
||||
TwentyUiButtonProperties,
|
||||
Record<string, never>,
|
||||
Record<string, never>,
|
||||
HtmlCommonEvents
|
||||
>({
|
||||
properties: {
|
||||
...HTML_COMMON_PROPERTIES_CONFIG,
|
||||
variant: { type: String },
|
||||
accent: { type: String },
|
||||
size: { type: String },
|
||||
disabled: { type: Boolean },
|
||||
fullWidth: { type: Boolean },
|
||||
},
|
||||
events: [...HTML_COMMON_EVENTS_ARRAY],
|
||||
});
|
||||
customElements.define('html-div', HtmlDivElement);
|
||||
customElements.define('html-span', HtmlSpanElement);
|
||||
customElements.define('html-section', HtmlSectionElement);
|
||||
|
|
@ -651,6 +676,7 @@ customElements.define('html-th', HtmlThElement);
|
|||
customElements.define('html-td', HtmlTdElement);
|
||||
customElements.define('html-br', HtmlBrElement);
|
||||
customElements.define('html-hr', HtmlHrElement);
|
||||
customElements.define('twenty-ui-button', TwentyUiButtonElement);
|
||||
customElements.define('remote-root', RemoteRootElement);
|
||||
customElements.define('remote-fragment', RemoteFragmentElement);
|
||||
|
||||
|
|
@ -700,6 +726,7 @@ declare global {
|
|||
'html-td': InstanceType<typeof HtmlTdElement>;
|
||||
'html-br': InstanceType<typeof HtmlBrElement>;
|
||||
'html-hr': InstanceType<typeof HtmlHrElement>;
|
||||
'twenty-ui-button': InstanceType<typeof TwentyUiButtonElement>;
|
||||
'remote-root': InstanceType<typeof RemoteRootElement>;
|
||||
'remote-fragment': InstanceType<typeof RemoteFragmentElement>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"sourceRoot": "packages/twenty-ui/src",
|
||||
"projectType": "library",
|
||||
"tags": [
|
||||
"scope:frontend"
|
||||
"scope:shared"
|
||||
],
|
||||
"targets": {
|
||||
"build": {
|
||||
|
|
|
|||
|
|
@ -57675,6 +57675,7 @@ __metadata:
|
|||
react: "npm:^18.0.0"
|
||||
ts-morph: "npm:^25.0.0"
|
||||
tsx: "npm:^4.7.0"
|
||||
twenty-ui: "workspace:*"
|
||||
typescript: "npm:^5.9.2"
|
||||
uuid: "npm:^13.0.0"
|
||||
vite: "npm:^7.0.0"
|
||||
|
|
|
|||
Loading…
Reference in a new issue