Skip to content

Commit bca9b1f

Browse files
author
Hector Arce De Las Heras
committed
Introduced showShadowFrom Prop for Header Box Shadow Control
A new optional property, showShadowFrom, has been added to enhance the header component's flexibility. This property allows for the dynamic management of the header's box shadow, enabling developers to specify conditions under which the shadow should appear. This addition is aimed at providing more control over the visual styling of the header, catering to diverse design requirements and improving the user interface's adaptability.
1 parent 34d7cfd commit bca9b1f

File tree

6 files changed

+139
-3
lines changed

6 files changed

+139
-3
lines changed

src/components/header/header.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,23 @@ import { useStyles } from '@/hooks/useStyles/useStyles';
66
import { ErrorBoundary, FallbackComponent } from '@/provider/errorBoundary';
77

88
import { HeaderStandAlone } from './headerStandAlone';
9+
import { useHeaderShadow } from './hook/useHeaderShadow';
910
import { HeaderPropsStylesType, IHeader, IHeaderStandAlone } from './types';
1011

1112
const HeaderComponent = React.forwardRef(
1213
<V extends string | unknown>(
13-
{ variant, ctv, ...props }: IHeader<V>,
14+
{ variant, ctv, showShadowFrom, ...props }: IHeader<V>,
1415
ref: React.ForwardedRef<HTMLDivElement> | undefined | null
1516
): JSX.Element => {
1617
const styles = useStyles<HeaderPropsStylesType, V>(STYLES_NAME.HEADER, variant, ctv);
1718
const device = useMediaDevice();
19+
const { box_shadow } = styles?.container?.scrollShadow || {};
20+
const { headerRef } = useHeaderShadow({
21+
ref,
22+
shadow: { boxShadow: box_shadow, showShadowFrom },
23+
});
1824

19-
return <HeaderStandAlone {...props} ref={ref} device={device} styles={styles} />;
25+
return <HeaderStandAlone {...props} ref={headerRef} device={device} styles={styles} />;
2026
}
2127
);
2228
HeaderComponent.displayName = 'HeaderComponent';
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { fireEvent } from '@testing-library/react';
2+
import { renderHook } from '@testing-library/react-hooks';
3+
import React, { createRef } from 'react';
4+
5+
import { useHeaderShadow } from '../useHeaderShadow';
6+
7+
const mockRef = {
8+
current: {
9+
style: {
10+
setProperty: jest.fn(),
11+
removeProperty: jest.fn(),
12+
},
13+
},
14+
};
15+
16+
describe('useHeaderShadow', () => {
17+
beforeAll(() => {
18+
window.innerHeight = 100;
19+
});
20+
it('should return a ref', () => {
21+
const { result } = renderHook(() => useHeaderShadow({ ref: undefined, shadow: {} }));
22+
expect(result.current.headerRef).toBeDefined();
23+
});
24+
25+
it('should no set box-shadow when shadow is not defined', () => {
26+
const innerRefMock = jest.spyOn(React, 'useRef').mockReturnValue(mockRef);
27+
const ref = createRef<HTMLDivElement>();
28+
29+
renderHook(() => useHeaderShadow({ ref, shadow: {} }));
30+
31+
expect(mockRef.current.style.removeProperty).toHaveBeenCalledWith('top');
32+
expect(mockRef.current.style.removeProperty).toHaveBeenCalledWith('position');
33+
34+
innerRefMock.mockRestore();
35+
});
36+
37+
it('should set box-shadow style when scroll percentage is greater than showShadowFrom', () => {
38+
const innerRefMock = jest.spyOn(React, 'useRef').mockReturnValue(mockRef);
39+
const documentBodyScrollHeight = jest
40+
.spyOn(document.body, 'scrollHeight', 'get')
41+
.mockReturnValue(100);
42+
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
43+
const ref = createRef<HTMLDivElement>();
44+
45+
renderHook(() =>
46+
useHeaderShadow({
47+
ref,
48+
shadow: { showShadowFrom: 10, boxShadow: '10px 5px 5px black' },
49+
})
50+
);
51+
52+
fireEvent.scroll(window, { scrollY: 15 });
53+
54+
expect(addEventListenerSpy).toHaveBeenCalledWith('scroll', expect.any(Function));
55+
addEventListenerSpy.mockRestore();
56+
innerRefMock.mockRestore();
57+
documentBodyScrollHeight.mockRestore();
58+
});
59+
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { ForwardedRef, Ref, useEffect, useImperativeHandle, useRef } from 'react';
2+
3+
type HeaderShadowType = {
4+
boxShadow?: string;
5+
showShadowFrom?: number;
6+
};
7+
8+
type HeaderShadowProps = {
9+
ref: ForwardedRef<HTMLDivElement> | undefined;
10+
shadow?: HeaderShadowType;
11+
};
12+
13+
type HeaderShadowReturnValues = {
14+
headerRef: Ref<HTMLDivElement> | undefined;
15+
};
16+
17+
export const useHeaderShadow = ({ ref, shadow }: HeaderShadowProps): HeaderShadowReturnValues => {
18+
const headerRef = useRef<HTMLElement | null>(null);
19+
20+
useImperativeHandle(
21+
ref,
22+
() => {
23+
return headerRef?.current as HTMLDivElement;
24+
},
25+
[]
26+
);
27+
28+
useEffect(() => {
29+
if (!headerRef.current) {
30+
return;
31+
}
32+
if (!shadow?.showShadowFrom || !shadow.boxShadow) {
33+
headerRef.current.style.removeProperty('top');
34+
headerRef.current.style.removeProperty('position');
35+
return;
36+
}
37+
38+
const header = headerRef.current;
39+
const { showShadowFrom, boxShadow } = shadow;
40+
header.style.setProperty('top', '0');
41+
header.style.setProperty('position', 'sticky');
42+
43+
const handleScroll = () => {
44+
const scrollPercentage = (window.scrollY / document.body.scrollHeight) * 100;
45+
if (scrollPercentage >= showShadowFrom) {
46+
header.style.setProperty('box-shadow', boxShadow);
47+
} else {
48+
header.style.removeProperty('box-shadow');
49+
}
50+
};
51+
52+
window.addEventListener('scroll', handleScroll);
53+
return () => {
54+
window.removeEventListener('scroll', handleScroll);
55+
};
56+
}, [shadow]);
57+
58+
return { headerRef: headerRef as Ref<HTMLDivElement> | undefined };
59+
};

src/components/header/stories/argtypes.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,17 @@ export const argtypes = (variants: IThemeObjectVariants, themeSelected: string):
6666
category: CATEGORY_CONTROL.TESTING,
6767
},
6868
},
69+
showShadowFrom: {
70+
description: 'Percentage number to display component shadow',
71+
control: { type: 'number' },
72+
type: { name: 'number' },
73+
table: {
74+
type: {
75+
summary: 'number',
76+
},
77+
category: CATEGORY_CONTROL.MODIFIERS,
78+
},
79+
},
6980
ctv: {
7081
description: 'Object used for update variant styles',
7182
type: { name: 'object' },

src/components/header/types/header.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface IHeaderStandAlone {
1212
crumbs?: CrumbType[];
1313
device: DeviceBreakpointsType;
1414
dataTestId?: string;
15+
showShadowFrom?: number;
1516
}
1617

1718
export interface IHeader<V = undefined extends string ? unknown : string>

src/components/header/types/headerTheme.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export type HeaderPropsStyles = {
55
};
66

77
export type HeaderPropsStylesType = {
8-
container?: CommonStyleType;
8+
container?: CommonStyleType & { scrollShadow?: Pick<CommonStyleType, 'box_shadow'> };
99
breadcrumbs?: CommonStyleType;
1010
content?: CommonStyleType;
1111
leftContent?: CommonStyleType;

0 commit comments

Comments
 (0)