Rename twenty-ui to twenty-ui-deprecated and twenty-new-ui to twenty-ui to prepare package release (#21315)

## Description

Promotes the next-gen UI library (formerly `twenty-new-ui`) to the name
**`twenty-ui`** (v0.1.0, publishable) and renames the old package to
**`twenty-ui-deprecated`**. Rewrites ~1,730 `twenty-ui` imports →
`twenty-ui-deprecated`, updates all configs/CI/Docker/deps, and migrates
twenty-front's `Toggle` to the new package (first consumer) as a
drop-in.

## Next steps
- Wire the `ui/v*` publish dispatch (`cd-deploy-tag.yaml` +
`.yarnrc.yml`), then tag `ui/v0.1.0` to publish.
- Continue migrating components from `twenty-ui-deprecated` →
`twenty-ui`.
This commit is contained in:
Raphaël Bosi
2026-06-08 18:12:28 +02:00
committed by GitHub
parent a91e737e69
commit c596a5e342
2320 changed files with 6313 additions and 4856 deletions
+1 -8
View File
@@ -7,11 +7,4 @@
* |___/
*/
export { Loader } from './loader/components/Loader';
export { CircularProgressBar } from './progress-bar/components/CircularProgressBar';
export type {
ProgressBarProps,
StyledBarProps,
} from './progress-bar/components/ProgressBar';
export { ProgressBar } from './progress-bar/components/ProgressBar';
export { useProgressAnimation } from './progress-bar/hooks/useProgressAnimation';
export {};
@@ -1,96 +0,0 @@
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { expect, waitFor } from 'storybook/test';
import { ComponentDecorator } from '@ui/testing';
import { Loader } from '../components/Loader';
const meta: Meta<typeof Loader> = {
title: 'UI/Feedback/Loader',
component: Loader,
decorators: [ComponentDecorator],
};
export default meta;
type Story = StoryObj<typeof Loader>;
export const WithColor: Story = {
args: {
color: 'red',
},
play: async ({ canvasElement }) => {
await waitFor(() => {
const element = canvasElement.querySelector(':first-child');
expect(element).toBeVisible();
return element;
});
},
};
export const WithDefaultCssVariable: Story = {
decorators: [
(Story) => (
// @ts-expect-error: Custom CSS variable for demonstration purposes
<div style={{ '--tw-button-color': 'blue' }}>
<Story />
</div>
),
],
play: async ({ canvasElement }) => {
await waitFor(() => {
const element = canvasElement.querySelector(':first-child');
expect(element).toBeVisible();
return element;
});
},
};
export const WithDefaultColor: Story = {
play: async ({ canvasElement }) => {
await waitFor(() => {
const element = canvasElement.querySelector(':first-child');
expect(element).toBeVisible();
return element;
});
},
};
export const WithDifferentColors: Story = {
render: () => (
<div id="container" style={{ display: 'flex', gap: '16px' }}>
<Loader color="red" />
<Loader color="blue" />
<Loader color="yellow" />
<Loader color="green" />
</div>
),
play: async ({ canvasElement }) => {
const loaders = await waitFor(() => {
const elements = canvasElement.querySelectorAll('#container > *');
expect(elements).toHaveLength(4);
return elements;
});
expect(loaders[0]).toHaveStyle({
borderColor: expect.stringContaining('red'),
});
expect(loaders[1]).toHaveStyle({
borderColor: expect.stringContaining('blue'),
});
expect(loaders[2]).toHaveStyle({
borderColor: expect.stringContaining('yellow'),
});
expect(loaders[3]).toHaveStyle({
borderColor: expect.stringContaining('green'),
});
},
};
@@ -1,61 +0,0 @@
import { styled } from '@linaria/react';
import { motion } from 'framer-motion';
import { type ThemeColor } from '@ui/theme';
import { themeCssVariables } from '@ui/theme-constants';
const StyledLoaderContainer = styled.div<{
color?: ThemeColor;
}>`
box-sizing: border-box;
justify-content: center;
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[2]};
width: ${themeCssVariables.spacing[6]};
height: ${themeCssVariables.spacing[3]};
border-radius: ${themeCssVariables.border.radius.pill};
border: 1px solid
${({ color }) =>
color
? themeCssVariables.tag.text[color]
: `var(--tw-button-color, ${themeCssVariables.font.color.tertiary})`};
overflow: hidden;
`;
const StyledLoaderBase = styled.div<{
color?: ThemeColor;
}>`
background-color: ${({ color }) =>
color
? themeCssVariables.tag.text[color]
: `var(--tw-button-color, ${themeCssVariables.font.color.tertiary})`};
border-radius: ${themeCssVariables.border.radius.pill};
height: 8px;
width: 8px;
`;
const StyledLoader = motion.create(StyledLoaderBase);
type LoaderProps = {
color?: ThemeColor;
};
export const Loader = ({ color }: LoaderProps) => {
return (
<StyledLoaderContainer color={color}>
<StyledLoader
color={color}
animate={{
x: [-16, 0, 16],
width: [8, 12, 8],
height: [8, 2, 8],
}}
transition={{
duration: 0.8,
times: [0, 0.15, 0.3],
repeat: Infinity,
}}
/>
</StyledLoaderContainer>
);
};
@@ -1,71 +0,0 @@
import { motion, useAnimation } from 'framer-motion';
import { useEffect, useMemo } from 'react';
interface CircularProgressBarProps {
size?: number;
barWidth?: number;
barColor?: string;
}
export const CircularProgressBar = ({
size = 50,
barWidth = 5,
barColor = 'currentColor',
}: CircularProgressBarProps) => {
const controls = useAnimation();
const circumference = useMemo(
() => 2 * Math.PI * (size / 2 - barWidth),
[size, barWidth],
);
useEffect(() => {
const animateIndeterminate = async () => {
const baseSegment = Math.max(5, circumference / 10); // Adjusting for smaller values
// Adjusted sequence based on baseSegment
const dashSequences = [
`${baseSegment} ${circumference - baseSegment}`,
`${baseSegment * 2} ${circumference - baseSegment * 2}`,
`${baseSegment * 3} ${circumference - baseSegment * 3}`,
`${baseSegment * 2} ${circumference - baseSegment * 2}`,
`${baseSegment} ${circumference - baseSegment}`,
];
await controls.start({
strokeDasharray: dashSequences,
rotate: [0, 720],
transition: {
strokeDasharray: {
duration: 2,
ease: 'linear',
repeat: Infinity,
repeatType: 'loop',
},
rotate: {
duration: 2,
ease: 'linear',
repeat: Infinity,
repeatType: 'loop',
},
},
});
};
animateIndeterminate();
}, [circumference, controls]);
return (
<motion.svg width={size} height={size} animate={controls}>
<motion.circle
cx={size / 2}
cy={size / 2}
r={size / 2 - barWidth}
fill="none"
stroke={barColor}
strokeWidth={barWidth}
strokeLinecap="round"
/>
</motion.svg>
);
};
@@ -1,70 +0,0 @@
import { styled } from '@linaria/react';
import { themeCssVariables } from '@ui/theme-constants';
import { motion } from 'framer-motion';
export type ProgressBarProps = {
value: number;
className?: string;
barColor?: string;
backgroundColor?: string;
withBorderRadius?: boolean;
};
export type StyledBarProps = {
className?: string;
backgroundColor?: string;
withBorderRadius?: boolean;
};
const StyledBar = styled.div<StyledBarProps>`
height: ${themeCssVariables.spacing[2]};
background-color: ${({ backgroundColor }) => backgroundColor ?? ''};
border-radius: ${({ withBorderRadius }) =>
withBorderRadius ? themeCssVariables.border.radius.xxl : '0'};
overflow: hidden;
width: 100%;
`;
const StyledBarFilling = styled.div<{
barColor?: string;
withBorderRadius?: boolean;
}>`
background-color: ${({ barColor }) =>
barColor ?? themeCssVariables.font.color.primary};
border-radius: ${({ withBorderRadius }) =>
withBorderRadius ? themeCssVariables.border.radius.md : '0'};
height: 100%;
width: 100%;
`;
const MIN_BAR_WIDTH_PX = 12;
export const ProgressBar = ({
value,
className,
barColor,
backgroundColor = 'none',
withBorderRadius = false,
}: ProgressBarProps) => (
<StyledBar
className={className}
backgroundColor={backgroundColor}
withBorderRadius={withBorderRadius}
role="progressbar"
aria-valuenow={Math.ceil(value)}
>
<motion.div
style={{
height: '100%',
minWidth: value > 0 ? MIN_BAR_WIDTH_PX : 0,
}}
animate={{ width: `${Math.ceil(value)}%` }}
transition={{ duration: 0.3, ease: 'linear' }}
>
<StyledBarFilling
barColor={barColor}
withBorderRadius={withBorderRadius}
/>
</motion.div>
</StyledBar>
);
@@ -1,54 +0,0 @@
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { CatalogDecorator, type CatalogStory } from '@ui/testing';
import { ComponentDecorator } from '@ui/testing/decorators/ComponentDecorator';
import { CircularProgressBar } from '../CircularProgressBar';
const meta: Meta<typeof CircularProgressBar> = {
title: 'UI/Feedback/CircularProgressBar/CircularProgressBar',
component: CircularProgressBar,
args: {
size: 50,
},
parameters: {
chromatic: { disableSnapshot: true },
},
};
export default meta;
type Story = StoryObj<typeof CircularProgressBar>;
export const Default: Story = {
decorators: [ComponentDecorator],
};
export const Catalog: CatalogStory<Story, typeof CircularProgressBar> = {
argTypes: {},
parameters: {
catalog: {
dimensions: [
{
name: 'barColor',
values: [undefined, 'red'],
props: (barColor: string) => ({ barColor }),
labels: (color: string) => `Segment Color: ${color ?? 'default'}`,
},
{
name: 'barWidth',
values: [undefined, 5, 10],
props: (barWidth: number) => ({ barWidth }),
labels: (width: number) =>
`Stroke Width: ${width ? width + ' px' : 'default'}`,
},
{
name: 'size',
values: [undefined, 80, 30],
props: (size: number) => ({ size }),
labels: (size: number) => `Size: ${size ? size + ' px' : 'default'}`,
},
],
},
},
decorators: [CatalogDecorator],
};
@@ -1,48 +0,0 @@
import { type Meta, type StoryObj } from '@storybook/react-vite';
import { useProgressAnimation } from '@ui/feedback/progress-bar/hooks/useProgressAnimation';
import { ComponentDecorator } from '@ui/testing/decorators/ComponentDecorator';
import { ProgressBar } from '../ProgressBar';
const meta: Meta<typeof ProgressBar> = {
title: 'UI/Feedback/ProgressBar/ProgressBar',
component: ProgressBar,
decorators: [ComponentDecorator],
argTypes: {
className: { control: false },
value: { control: { type: 'range', min: 0, max: 100, step: 1 } },
},
};
export default meta;
type Story = StoryObj<typeof ProgressBar>;
export const Default: Story = {
args: {
value: 75,
},
};
export const Animated: Story = {
tags: ['!test'],
argTypes: {
value: { control: false },
},
decorators: [
(Story) => {
const { value } = useProgressAnimation({
autoPlay: true,
initialValue: 0,
finalValue: 100,
options: {
duration: 10000,
},
});
return <Story args={{ value }} />;
},
],
parameters: {
chromatic: { disableSnapshot: true },
},
};
@@ -1,57 +0,0 @@
import { millisecondsToSeconds } from 'date-fns';
import {
animate,
type AnimationPlaybackControls,
type ValueAnimationTransition,
} from 'framer-motion';
import { useCallback, useEffect, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
export const useProgressAnimation = ({
autoPlay = true,
initialValue = 0,
finalValue = 100,
options,
}: {
autoPlay?: boolean;
initialValue?: number;
finalValue?: number;
options?: ValueAnimationTransition<number>;
}) => {
const [animation, setAnimation] = useState<
AnimationPlaybackControls | undefined
>();
const [value, setValue] = useState(initialValue);
const startAnimation = useCallback(() => {
if (isDefined(animation)) return;
const duration = isDefined(options?.duration)
? millisecondsToSeconds(options.duration)
: undefined;
setAnimation(
animate(initialValue, finalValue, {
...options,
duration,
onUpdate: (nextValue) => {
if (value === nextValue) return;
setValue(nextValue);
options?.onUpdate?.(nextValue);
},
}),
);
}, [animation, finalValue, initialValue, options, value]);
useEffect(() => {
if (autoPlay && !animation) {
startAnimation();
}
}, [animation, autoPlay, startAnimation]);
return {
animation,
startAnimation,
value,
};
};