[FRONT COMPONENTS] Twenty UI elements generation (#17866)

## PR description

This PR:
- Creates a TypeScript-based extractor that discovers all exported
twenty-ui components by scanning barrel files, extracting
props/slots/events via ts-morph type analysis, and generating the remote
DOM bindings automatically.

- Adds a new ESLint rule which enforces all *Props types in twenty-ui
components to be exported, which is required for the extractor to
discover component prop types. Existing twenty-ui components are updated
to comply with this rule.

- Extends the remote DOM generation to support slots, per-component
events, forwardRef wrappers, and richer property types (array, object,
function)

## Edge cases to fix in another PR

- Icons cannot be rendered inside buttons
- IconButtons throw an error when mounted
- MenuItems are not displayed correctly
- MenuItemNavigate throws on click

## Video Demo


https://github.com/user-attachments/assets/c2ed67cf-6a15-4896-9fec-e83fac0e862b
This commit is contained in:
Raphaël Bosi
2026-02-12 12:48:16 +01:00
committed by GitHub
parent c48ccbca99
commit cb7d0b83a8
133 changed files with 10556 additions and 984 deletions
@@ -5,7 +5,7 @@ const StyledSoonPill = styled(Pill)`
margin-left: auto;
`;
type ButtonSoonProps = {
export type ButtonSoonProps = {
label?: string;
};
@@ -6,7 +6,7 @@ import {
type LightIconButtonProps,
} from '@ui/input/button/components/LightIconButton';
type ColorPickerButtonProps = Pick<ColorSampleProps, 'colorName'> &
export type ColorPickerButtonProps = Pick<ColorSampleProps, 'colorName'> &
Pick<LightIconButtonProps, 'onClick'> & {
isSelected?: boolean;
};
@@ -5,7 +5,7 @@ import React, { type FunctionComponent } from 'react';
export type MainButtonVariant = 'primary' | 'secondary';
type Props = {
export type Props = {
title: string;
fullWidth?: boolean;
width?: number;
@@ -102,7 +102,7 @@ const StyledButton = styled.button<
}};
`;
type MainButtonProps = Props & {
export type MainButtonProps = Props & {
Icon?: IconComponent | FunctionComponent<{ size: number }>;
};
@@ -28,7 +28,7 @@ const StyledIconButton = styled.button<{ size: RoundedIconButtonSize }>`
width: ${({ size }) => (size === 'small' ? '20px' : '24px')};
`;
type RoundedIconButtonProps = {
export type RoundedIconButtonProps = {
Icon: IconComponent;
size?: RoundedIconButtonSize;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;
@@ -4,7 +4,7 @@ import { TabContent } from '@ui/input/button/components/TabButton/internals/comp
import { type ReactElement } from 'react';
import { Link } from 'react-router-dom';
type TabButtonProps = {
export type TabButtonProps = {
id: string;
active?: boolean;
disabled?: boolean;
@@ -10,7 +10,7 @@ import { isDefined } from 'twenty-shared/utils';
type CodeEditorVariant = 'default' | 'with-header' | 'borderless';
type CodeEditorProps = Pick<
export type CodeEditorProps = Pick<
EditorProps,
'value' | 'language' | 'onMount' | 'onValidate' | 'height' | 'options'
> & {
@@ -23,7 +23,7 @@ const StyledRadioContainer = styled.div`
top: ${({ theme }) => theme.spacing(2)};
`;
type CardPickerProps = {
export type CardPickerProps = {
children: React.ReactNode;
handleChange?: () => void;
checked?: boolean;
@@ -24,7 +24,7 @@ export enum CheckboxAccent {
Orange = 'orange',
}
type CheckboxProps = {
export type CheckboxProps = {
checked: boolean;
indeterminate?: boolean;
hoverable?: boolean;
@@ -38,7 +38,7 @@ type CheckboxProps = {
accent?: CheckboxAccent;
};
type InputProps = {
export type InputProps = {
checkboxSize: CheckboxSize;
variant: CheckboxVariant;
accent?: CheckboxAccent;
@@ -1,7 +1,7 @@
import IconListViewGripRaw from '@assets/misc/list-view-grip.svg?react';
import { type IconComponentProps } from '@ui/display/icon/types/IconComponent';
type IconListViewGripProps = Pick<IconComponentProps, 'size' | 'stroke'>;
export type IconListViewGripProps = Pick<IconComponentProps, 'size' | 'stroke'>;
export const IconListViewGrip = (props: IconListViewGripProps) => {
const width = props.size ?? 8;
@@ -3,7 +3,7 @@ import { useTheme } from '@emotion/react';
import { type RadioProps } from './Radio';
type RadioGroupProps = React.PropsWithChildren & {
export type RadioGroupProps = React.PropsWithChildren & {
value?: string;
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
onValueChange?: (value: string) => void;
+16 -2
View File
@@ -22,6 +22,7 @@ export { Button } from './button/components/Button/Button';
export { baseTransitionTiming } from './button/components/Button/constant';
export type { ButtonGroupProps } from './button/components/ButtonGroup';
export { ButtonGroup } from './button/components/ButtonGroup';
export type { ColorPickerButtonProps } from './button/components/ColorPickerButton';
export { ColorPickerButton } from './button/components/ColorPickerButton';
export type {
FloatingButtonSize,
@@ -64,9 +65,16 @@ export type {
export { LightIconButton } from './button/components/LightIconButton';
export type { LightIconButtonGroupProps } from './button/components/LightIconButtonGroup';
export { LightIconButtonGroup } from './button/components/LightIconButtonGroup';
export type { MainButtonVariant } from './button/components/MainButton';
export type {
MainButtonVariant,
Props,
MainButtonProps,
} from './button/components/MainButton';
export { MainButton } from './button/components/MainButton';
export type { RoundedIconButtonSize } from './button/components/RoundedIconButton';
export type {
RoundedIconButtonSize,
RoundedIconButtonProps,
} from './button/components/RoundedIconButton';
export { RoundedIconButton } from './button/components/RoundedIconButton';
export {
StyledTabButton,
@@ -75,7 +83,9 @@ export {
} from './button/components/TabButton/internals/components/StyledTabBase';
export type { TabContentProps } from './button/components/TabButton/internals/components/TabContent';
export { TabContent } from './button/components/TabButton/internals/components/TabContent';
export type { TabButtonProps } from './button/components/TabButton/TabButton';
export { TabButton } from './button/components/TabButton/TabButton';
export type { CodeEditorProps } from './code-editor/components/CodeEditor';
export { CodeEditor } from './code-editor/components/CodeEditor';
export type { CoreEditorHeaderProps } from './code-editor/components/CodeEditorHeader';
export { CoreEditorHeader } from './code-editor/components/CodeEditorHeader';
@@ -88,7 +98,9 @@ export type {
export { ColorSchemeCard } from './color-scheme/components/ColorSchemeCard';
export type { ColorSchemePickerProps } from './color-scheme/components/ColorSchemePicker';
export { ColorSchemePicker } from './color-scheme/components/ColorSchemePicker';
export type { CardPickerProps } from './components/CardPicker';
export { CardPicker } from './components/CardPicker';
export type { CheckboxProps, InputProps } from './components/Checkbox';
export {
CheckboxVariant,
CheckboxShape,
@@ -96,9 +108,11 @@ export {
CheckboxAccent,
Checkbox,
} from './components/Checkbox';
export type { IconListViewGripProps } from './components/IconListViewGrip';
export { IconListViewGrip } from './components/IconListViewGrip';
export type { RadioProps } from './components/Radio';
export { RadioSize, LabelPosition, Radio } from './components/Radio';
export type { RadioGroupProps } from './components/RadioGroup';
export { RadioGroup } from './components/RadioGroup';
export type { SearchInputProps } from './components/SearchInput';
export { SearchInput } from './components/SearchInput';