Migrate to twenty-ui - feedback/progress-bar (#8002)
This PR was created by [GitStart](https://gitstart.com/) to address the requirements from this ticket: [TWNTY-7527](https://clients.gitstart.com/twenty/5449/tickets/TWNTY-7527). --- ### Description Migrate `feedback/progress-bar` to twenty ui: - CircularProgressBar - ProgressBar ### Demo ProgressBar on Storybook  CircularProgressBar on Storybook  ###### Fixes twentyhq/private-issues#91 Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com> Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
committed by
GitHub
parent
414f2ac498
commit
445ab83c14
@ -1,7 +1,6 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { CircularProgressBar } from '@/ui/feedback/progress-bar/components/CircularProgressBar';
|
||||
import { MainButton } from 'twenty-ui';
|
||||
import { CircularProgressBar, MainButton } from 'twenty-ui';
|
||||
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
@ -3,10 +3,10 @@ import styled from '@emotion/styled';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { useSpreadsheetImportInternal } from '@/spreadsheet-import/hooks/useSpreadsheetImportInternal';
|
||||
import { CircularProgressBar } from '@/ui/feedback/progress-bar/components/CircularProgressBar';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { Modal } from '@/ui/layout/modal/components/Modal';
|
||||
import { CircularProgressBar } from 'twenty-ui';
|
||||
|
||||
import { SpreadsheetImportStep } from '@/spreadsheet-import/steps/types/SpreadsheetImportStep';
|
||||
import { SpreadsheetImportStepType } from '@/spreadsheet-import/steps/types/SpreadsheetImportStepType';
|
||||
|
||||
@ -1,71 +0,0 @@
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { motion, useAnimation } from 'framer-motion';
|
||||
|
||||
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,43 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
export type ProgressBarProps = {
|
||||
className?: string;
|
||||
color?: string;
|
||||
value: number;
|
||||
};
|
||||
|
||||
export type StyledBarProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const StyledBar = styled.div<StyledBarProps>`
|
||||
height: ${({ theme }) => theme.spacing(2)};
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledBarFilling = styled(motion.div)<{ color?: string }>`
|
||||
background-color: ${({ color, theme }) => color ?? theme.font.color.primary};
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
export const ProgressBar = ({ className, color, value }: ProgressBarProps) => {
|
||||
const [initialValue] = useState(value);
|
||||
|
||||
return (
|
||||
<StyledBar
|
||||
className={className}
|
||||
role="progressbar"
|
||||
aria-valuenow={Math.ceil(value)}
|
||||
>
|
||||
<StyledBarFilling
|
||||
initial={{ width: `${initialValue}%` }}
|
||||
animate={{ width: `${value}%` }}
|
||||
color={color}
|
||||
transition={{ ease: 'linear' }}
|
||||
/>
|
||||
</StyledBar>
|
||||
);
|
||||
};
|
||||
@ -1,53 +0,0 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { CatalogDecorator, CatalogStory, ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
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,49 +0,0 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { useProgressAnimation } from '@/ui/feedback/progress-bar/hooks/useProgressAnimation';
|
||||
|
||||
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 = {
|
||||
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,58 +0,0 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { millisecondsToSeconds } from 'date-fns';
|
||||
import {
|
||||
animate,
|
||||
AnimationPlaybackControls,
|
||||
ValueAnimationTransition,
|
||||
} from 'framer-motion';
|
||||
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
@ -10,10 +10,10 @@ import {
|
||||
LightButton,
|
||||
LightIconButton,
|
||||
MOBILE_VIEWPORT,
|
||||
ProgressBar,
|
||||
useProgressAnimation,
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { ProgressBar } from '@/ui/feedback/progress-bar/components/ProgressBar';
|
||||
import { useProgressAnimation } from '@/ui/feedback/progress-bar/hooks/useProgressAnimation';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export enum SnackBarVariant {
|
||||
|
||||
Reference in New Issue
Block a user