Skip to content

Commit fad6e50

Browse files
author
Kubit
committed
Include animation on selectorBoxFile component
1 parent a5ec2c2 commit fad6e50

17 files changed

+619
-39
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { getBottomBarWith, getLeftBarHeight, getRightBarHeight, getTopBarWith } from '../utils';
2+
3+
describe('animation utils', () => {
4+
it('should return the total width', () => {
5+
const result = getTopBarWith(100);
6+
expect(result).toBe(100);
7+
});
8+
9+
it('should return the correct width for the top bar', () => {
10+
const result = getTopBarWith(10);
11+
expect(result).toBe(40);
12+
});
13+
14+
it('should return the correct width for the right bar', () => {
15+
const result = getRightBarHeight(30);
16+
expect(result).toBe(20);
17+
});
18+
19+
it('should return the correct width for the bottom bar', () => {
20+
const result = getBottomBarWith(60);
21+
expect(result).toBe(40);
22+
});
23+
24+
it('should return the correct width for the left bar', () => {
25+
const result = getLeftBarHeight(90);
26+
expect(result).toBe(60);
27+
});
28+
});

src/components/selectorBoxFile/__tests__/selectorBoxFile.test.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { TooltipAlignType } from '@/components/tooltip';
99
import { renderProvider } from '@/tests/renderProvider/renderProvider.utility';
1010
import { ROLES } from '@/types';
1111

12+
import * as stylesHook from '../../../hooks/useStyles/useStyles';
1213
import { SelectorBoxFile } from '../selectorBoxFile';
1314
import { SelectorBoxFileStateType } from '../types';
1415

@@ -77,6 +78,7 @@ describe('SelectorBoxFile', () => {
7778
// Fix in the future: Currently the tooltip have internal div, so the tooltip can not be used next to text
7879
rules: {
7980
'element-permitted-content': 'off',
81+
'no-inline-style': 'off',
8082
},
8183
});
8284
expect(results).toHaveNoViolations();
@@ -126,6 +128,7 @@ describe('SelectorBoxFile', () => {
126128
// Fix in the future: Currently the tooltip have internal div, so the tooltip can not be used next to text
127129
rules: {
128130
'element-permitted-content': 'off',
131+
'no-inline-style': 'off',
129132
},
130133
});
131134
expect(results).toHaveNoViolations();
@@ -180,6 +183,7 @@ describe('SelectorBoxFile', () => {
180183
// Fix in the future: Currently the tooltip have internal div, so the tooltip can not be used next to text
181184
rules: {
182185
'element-permitted-content': 'off',
186+
'no-inline-style': 'off',
183187
},
184188
});
185189
expect(results).toHaveNoViolations();
@@ -273,4 +277,73 @@ describe('SelectorBoxFile', () => {
273277
expect(onSizeError).not.toHaveBeenCalled();
274278
expect(onFileError).not.toHaveBeenCalled();
275279
});
280+
281+
it('May render animation containers', () => {
282+
const dataTestId = 'selectorBoxFileAnimations';
283+
const mockStyles = {
284+
states: {
285+
[SelectorBoxFileStateType.DEFAULT]: {
286+
animationContainer: {
287+
padding: '2px',
288+
position: 'relative',
289+
overflow: 'hidden',
290+
},
291+
topAnimationContainer: {
292+
height: '15px',
293+
top: '0px',
294+
left: '0px',
295+
transition: '500ms width',
296+
position: 'absolute',
297+
background: 'red',
298+
},
299+
rightAnimationContainer: {
300+
width: '15px',
301+
top: '0px',
302+
right: '0px',
303+
transition: '500ms height',
304+
position: 'absolute',
305+
background: 'red',
306+
},
307+
bottomAnimationContainer: {
308+
height: '15px',
309+
right: '0px',
310+
bottom: '0px',
311+
transition: '500ms width',
312+
position: 'absolute',
313+
background: 'red',
314+
},
315+
leftAnimationContainer: {
316+
width: '15px',
317+
bottom: '0px',
318+
left: '0px',
319+
transition: '500ms height',
320+
position: 'absolute',
321+
background: 'red',
322+
},
323+
},
324+
},
325+
};
326+
jest.spyOn(stylesHook, 'useStyles').mockImplementation(() => mockStyles);
327+
const { getByTestId } = renderProvider(<SelectorBoxFile {...mockProps} percentage={10} />);
328+
const animationContainer = getByTestId(dataTestId);
329+
expect(animationContainer).toBeInTheDocument();
330+
});
331+
332+
it('May not render animation containers', () => {
333+
const dataTestId = 'selectorBoxFileAnimations';
334+
const mockStyles = {
335+
states: {
336+
[SelectorBoxFileStateType.DEFAULT]: {
337+
animationContainer: undefined,
338+
topAnimationContainer: undefined,
339+
rightAnimationContainer: undefined,
340+
bottomAnimationContainer: undefined,
341+
leftAnimationContainer: undefined,
342+
},
343+
},
344+
};
345+
jest.spyOn(stylesHook, 'useStyles').mockImplementation(() => mockStyles);
346+
const { queryByTestId } = renderProvider(<SelectorBoxFile {...mockProps} />);
347+
expect(queryByTestId(dataTestId)).not.toBeInTheDocument();
348+
});
276349
});
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { waitFor } from '@testing-library/react';
2+
import { renderHook } from '@testing-library/react-hooks';
3+
4+
import { useBorderAnimation } from '../hooks';
5+
import { getBottomBarWith, getLeftBarHeight, getRightBarHeight, getTopBarWith } from '../utils';
6+
7+
// Mock the utility functions
8+
jest.mock('../utils', () => ({
9+
getTopBarWith: jest.fn(),
10+
getRightBarHeight: jest.fn(),
11+
getBottomBarWith: jest.fn(),
12+
getLeftBarHeight: jest.fn(),
13+
}));
14+
15+
describe('useBorderAnimation', () => {
16+
beforeEach(() => {
17+
jest.clearAllMocks();
18+
});
19+
20+
it('should initialize refs correctly', () => {
21+
const { result } = renderHook(() => useBorderAnimation({ percentage: 0 }));
22+
23+
expect(result.current.topRef).toBeDefined();
24+
expect(result.current.rightRef).toBeDefined();
25+
expect(result.current.bottomRef).toBeDefined();
26+
expect(result.current.leftRef).toBeDefined();
27+
});
28+
29+
it('should update topRef width based on percentage', () => {
30+
(getTopBarWith as jest.Mock).mockReturnValue(50);
31+
32+
const { result, rerender } = renderHook(
33+
({ percentage }) => useBorderAnimation({ percentage }),
34+
{
35+
initialProps: { percentage: 10 },
36+
}
37+
);
38+
39+
rerender({ percentage: 20 });
40+
41+
const ref = (result.current.topRef as React.MutableRefObject<HTMLDivElement>)?.current;
42+
43+
waitFor(() => {
44+
expect(ref?.style.width).toBe('50%');
45+
});
46+
});
47+
48+
it('should update rightRef height based on percentage', () => {
49+
(getRightBarHeight as jest.Mock).mockReturnValue(60);
50+
51+
const { result, rerender } = renderHook(
52+
({ percentage }) => useBorderAnimation({ percentage }),
53+
{
54+
initialProps: { percentage: 30 },
55+
}
56+
);
57+
58+
rerender({ percentage: 40 });
59+
60+
const ref = (result.current.rightRef as React.MutableRefObject<HTMLDivElement>)?.current;
61+
62+
waitFor(() => {
63+
expect(ref?.style.height).toBe('60%');
64+
});
65+
});
66+
67+
it('should update bottomRef width based on percentage', () => {
68+
(getBottomBarWith as jest.Mock).mockReturnValue(50);
69+
70+
const { result, rerender } = renderHook(
71+
({ percentage }) => useBorderAnimation({ percentage }),
72+
{
73+
initialProps: { percentage: 60 },
74+
}
75+
);
76+
77+
rerender({ percentage: 70 });
78+
79+
const ref = (result.current.bottomRef as React.MutableRefObject<HTMLDivElement>)?.current;
80+
81+
waitFor(() => {
82+
expect(ref?.style.width).toBe('50%');
83+
});
84+
});
85+
86+
it('should update leftRef height based on percentage', () => {
87+
(getLeftBarHeight as jest.Mock).mockReturnValue(50);
88+
89+
const { result, rerender } = renderHook(
90+
({ percentage }) => useBorderAnimation({ percentage }),
91+
{
92+
initialProps: { percentage: 80 },
93+
}
94+
);
95+
96+
rerender({ percentage: 90 });
97+
98+
const ref = (result.current.leftRef as React.MutableRefObject<HTMLDivElement>)?.current;
99+
waitFor(() => {
100+
expect(ref?.style.height).toBe('50%');
101+
});
102+
});
103+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './selectorBoxFileHeader';
22
export * from './selectorBoxFileErrorMessage';
33
export * from './selectorBoxFileContainerBox';
4+
export * from './selectorBoxFileAnimation';
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import * as React from 'react';
2+
3+
import { useBorderAnimation } from '../hooks';
4+
import {
5+
AnimationContainerStyled,
6+
BorderContainerStyled,
7+
BottomAnimationStyled,
8+
LeftAnimationStyled,
9+
RightAnimationStyled,
10+
TopAnimationStyled,
11+
} from '../selectorBoxFile.styled';
12+
import { SelectorBoxFilePropsStylesType, SelectorBoxFileStateType } from '../types';
13+
import { ISelectorBoxFileContainerBox } from './selectorBoxFileContainerBox';
14+
15+
interface ISelectorBoxFileAnimation {
16+
state: SelectorBoxFileStateType;
17+
styles: SelectorBoxFilePropsStylesType;
18+
percentage: number;
19+
focus: boolean;
20+
dataTestId?: string;
21+
onAnimationCompleted?: () => void;
22+
}
23+
24+
export const SelectorBoxFileAnimation = ({
25+
...props
26+
}: React.PropsWithChildren<ISelectorBoxFileAnimation>): JSX.Element => {
27+
const { topRef, rightRef, bottomRef, leftRef } = useBorderAnimation({
28+
percentage: props.percentage,
29+
styles: props.styles?.states?.[props.state],
30+
onAnimationCompleted: props.onAnimationCompleted,
31+
});
32+
33+
const animationContainer = props.styles?.states?.[props.state]?.animationContainer;
34+
const topAnimationContainer = props.styles?.states?.[props.state]?.topAnimationContainer;
35+
const rightAnimationContainer = props.styles?.states?.[props.state]?.rightAnimationContainer;
36+
const bottomAnimationContainer = props.styles?.states?.[props.state]?.bottomAnimationContainer;
37+
const leftAnimationContainer = props.styles?.states?.[props.state]?.leftAnimationContainer;
38+
39+
const childrenWithProps = React.Children.map(props.children, child => {
40+
if (React.isValidElement<ISelectorBoxFileContainerBox>(child)) {
41+
return React.cloneElement(child, { focus: props.focus });
42+
}
43+
return child;
44+
});
45+
46+
return animationContainer ? (
47+
<AnimationContainerStyled
48+
data-focus={props.focus}
49+
data-testid={`${props.dataTestId}Animations`}
50+
state={props.state}
51+
styles={props.styles}
52+
>
53+
{/* Animation containers */}
54+
{topAnimationContainer && (
55+
<TopAnimationStyled ref={topRef} state={props.state} styles={props.styles} />
56+
)}
57+
{rightAnimationContainer && (
58+
<RightAnimationStyled ref={rightRef} state={props.state} styles={props.styles} />
59+
)}
60+
{bottomAnimationContainer && (
61+
<BottomAnimationStyled ref={bottomRef} state={props.state} styles={props.styles} />
62+
)}
63+
{leftAnimationContainer && (
64+
<LeftAnimationStyled ref={leftRef} state={props.state} styles={props.styles} />
65+
)}
66+
<BorderContainerStyled state={props.state} styles={props.styles}>
67+
{props.children}
68+
</BorderContainerStyled>
69+
</AnimationContainerStyled>
70+
) : (
71+
<>{childrenWithProps}</>
72+
);
73+
};

src/components/selectorBoxFile/components/selectorBoxFileContainerBox.tsx

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,18 @@ import {
1717
SelectorBoxFileStateType,
1818
} from '../types';
1919

20-
interface ISelectorBoxFileContainerBox {
20+
export interface ISelectorBoxFileContainerBox {
2121
styles: SelectorBoxFilePropsStylesType;
2222
htmlFor: string;
2323
state: SelectorBoxFileStateType;
2424
containerBoxStateContent: SelectorBoxFileContainerBoxStateContentType;
2525
filename?: string;
26-
focus: boolean;
26+
focus?: boolean;
2727
}
2828

29-
export const SelectorBoxFileContainerBox = (props: ISelectorBoxFileContainerBox): JSX.Element => {
29+
export const SelectorBoxFileContainerBox = ({
30+
...props
31+
}: ISelectorBoxFileContainerBox): JSX.Element => {
3032
return (
3133
<ContainerBoxStyled
3234
data-focus={props.focus}
@@ -35,22 +37,22 @@ export const SelectorBoxFileContainerBox = (props: ISelectorBoxFileContainerBox)
3537
styles={props.styles}
3638
>
3739
<span>
38-
<Loader
39-
altText={props.containerBoxStateContent[props.state]?.icon?.altText}
40-
variant={
41-
props.styles?.states?.[SelectorBoxFileStateType.LOADING]?.containerBoxLoader?.variant
42-
}
43-
visible={props.state === SelectorBoxFileStateType.LOADING}
44-
width={
45-
props.styles?.states?.[SelectorBoxFileStateType.LOADING]?.containerBoxLoader?.width
46-
}
47-
/>
48-
{props.state !== SelectorBoxFileStateType.LOADING && (
49-
<ElementOrIcon
50-
customIconStyles={props.styles?.states?.[props.state]?.containerBoxIcon}
51-
{...props.containerBoxStateContent[props.state]?.icon}
40+
{props.styles?.states?.[SelectorBoxFileStateType.LOADING]?.containerBoxLoader?.variant && (
41+
<Loader
42+
altText={props.containerBoxStateContent[props.state]?.icon?.altText}
43+
variant={
44+
props.styles?.states?.[SelectorBoxFileStateType.LOADING]?.containerBoxLoader?.variant
45+
}
46+
visible={props.state === SelectorBoxFileStateType.LOADING}
47+
width={
48+
props.styles?.states?.[SelectorBoxFileStateType.LOADING]?.containerBoxLoader?.width
49+
}
5250
/>
5351
)}
52+
<ElementOrIcon
53+
customIconStyles={props.styles?.states?.[props.state]?.containerBoxIcon}
54+
{...props.containerBoxStateContent[props.state]?.icon}
55+
/>
5456
</span>
5557
<ContainerBoxTextWrapper state={props.state} styles={props.styles}>
5658
{props.filename &&
@@ -70,7 +72,6 @@ export const SelectorBoxFileContainerBox = (props: ISelectorBoxFileContainerBox)
7072
{props.containerBoxStateContent[props.state]?.actionText?.content ? (
7173
<ActionIconAndActionTextContainerStyled state={props.state} styles={props.styles}>
7274
<ElementOrIcon
73-
altText={props.containerBoxStateContent[props.state]?.icon?.altText}
7475
customIconStyles={props.styles?.states?.[props.state]?.actionIcon}
7576
{...props.containerBoxStateContent[props.state]?.actionIcon}
7677
/>
@@ -94,6 +95,14 @@ export const SelectorBoxFileContainerBox = (props: ISelectorBoxFileContainerBox)
9495
) : null}
9596
</ContainerBoxActionDescriptionTextWrapper>
9697
</ContainerBoxTextWrapper>
98+
{props.containerBoxStateContent[props.state]?.iconRight && (
99+
<span>
100+
<ElementOrIcon
101+
customIconStyles={props.styles?.states?.[props.state]?.containerBoxIcon}
102+
{...props.containerBoxStateContent[props.state]?.iconRight}
103+
/>
104+
</span>
105+
)}
97106
</ContainerBoxStyled>
98107
);
99108
};

0 commit comments

Comments
 (0)