Skip to content

Commit ca9111d

Browse files
committed
feat(): Adding navigation bar
1 parent 6154f75 commit ca9111d

18 files changed

Lines changed: 328 additions & 16 deletions

File tree

craco.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ module.exports = {
66
'@assets': path.resolve(__dirname, './src/assets'),
77
'@assets/*': path.resolve(__dirname, './src/assets/*'),
88
'@data': path.resolve(__dirname, './src/data'),
9+
'@hooks': path.resolve(__dirname, './src/hooks'),
910
'@localization': path.resolve(__dirname, './src/localization'),
1011
'@components': path.resolve(__dirname, './src/components'),
1112
'@providers': path.resolve(__dirname, './src/providers'),

src/App.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@ import {
55
ThemeProvider,
66
} from '@providers';
77
import { HelmetProvider } from 'react-helmet-async';
8+
import { CursorProvider } from '@components';
89

910
function App() {
1011
return (
1112
<ThemeProvider>
1213
<React.StrictMode>
1314
<HelmetProvider>
14-
<LocalizationProvider>
15-
<AppRouterProvider />
16-
</LocalizationProvider>
15+
<CursorProvider>
16+
<LocalizationProvider>
17+
<AppRouterProvider />
18+
</LocalizationProvider>
19+
</CursorProvider>
1720
</HelmetProvider>
1821
</React.StrictMode>
1922
</ThemeProvider>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { SVGProps } from 'react';
2+
const ArrowIcon = (props: SVGProps<SVGSVGElement>) => (
3+
<svg
4+
width="1em"
5+
height="1em"
6+
fill="currentColor"
7+
viewBox="0 0 16 16"
8+
{...props}
9+
>
10+
<path
11+
fillRule="evenodd"
12+
d="M6.364 13.5a.5.5 0 0 0 .5.5H13.5a1.5 1.5 0 0 0 1.5-1.5v-10A1.5 1.5 0 0 0 13.5 1h-10A1.5 1.5 0 0 0 2 2.5v6.636a.5.5 0 1 0 1 0V2.5a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 .5.5v10a.5.5 0 0 1-.5.5H6.864a.5.5 0 0 0-.5.5"
13+
/>
14+
<path
15+
fillRule="evenodd"
16+
d="M11 5.5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793l-8.147 8.146a.5.5 0 0 0 .708.708L10 6.707V10.5a.5.5 0 0 0 1 0z"
17+
/>
18+
</svg>
19+
);
20+
export default ArrowIcon;

src/assets/icons/Common/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { SearchIcon } from './Search';
22
export { ThreeDIcon } from './3D';
33
export { TwoDIcon } from './2D';
4+
export { default as ArrowIcon } from './ArrowIcon';
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { Button, Text } from '@chakra-ui/react';
2+
import { LinkButtonProps } from './types';
3+
import { ArrowIcon } from '@assets';
4+
import { useCursor } from '../../Cursor';
5+
import { useRef, useState } from 'react';
6+
import { useMousePositions } from '@hooks';
7+
8+
const LinkButton = ({
9+
text,
10+
href,
11+
animationOnHover,
12+
withUnderline,
13+
withArrow,
14+
fontSize,
15+
}: LinkButtonProps) => {
16+
const ref = useRef<HTMLButtonElement>(null);
17+
const { toggle, setCursorInsets } = useCursor();
18+
const [isHovered, setIsHovered] = useState(false);
19+
20+
const onMouseEnter = () => {
21+
const { width, height, top, left } =
22+
ref.current?.getBoundingClientRect() || {
23+
width: 56,
24+
height: 56,
25+
top: 0,
26+
left: 0,
27+
};
28+
setCursorInsets(undefined);
29+
setTimeout(() => {
30+
setCursorInsets({ height, width, top, left });
31+
}, 0);
32+
toggle();
33+
setIsHovered(true);
34+
};
35+
36+
const onMouseLeave = () => {
37+
setCursorInsets(undefined);
38+
setIsHovered(false);
39+
toggle();
40+
};
41+
42+
const translationProps = isHovered
43+
? {
44+
transform: 'translateY(15px)',
45+
transition: 'transform 0.2s',
46+
}
47+
: {
48+
transform: 'translateY(-11px)',
49+
transition: 'transform 0.2s',
50+
};
51+
52+
return (
53+
<Button
54+
ref={ref}
55+
flexDir={'column'}
56+
as="a"
57+
href={href}
58+
bg={'transparent'}
59+
color={'white'}
60+
colorScheme="violet"
61+
fontSize={fontSize}
62+
borderRadius={0}
63+
overflow={'clip'}
64+
px={2}
65+
maxHeight={'7'}
66+
_hover={{ color: 'violet' }}
67+
borderBottom={withUnderline ? '1px solid white' : 'none'}
68+
rightIcon={withArrow ? <ArrowIcon /> : undefined}
69+
onMouseEnter={onMouseEnter}
70+
onMouseLeave={onMouseLeave}
71+
justifyItems={'end'}
72+
>
73+
<Text
74+
pointerEvents={'none'}
75+
{...(animationOnHover ? translationProps : {})}
76+
>
77+
{text}
78+
</Text>
79+
{animationOnHover && (
80+
<Text pointerEvents={'none'} {...translationProps}>
81+
{text}
82+
</Text>
83+
)}
84+
</Button>
85+
);
86+
};
87+
88+
export default LinkButton;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as LinkButton } from './LinkButton';
2+
export * from './types';
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { BoxProps } from '@chakra-ui/react';
2+
3+
export type LinkButtonProps = {
4+
href: string;
5+
text: string;
6+
withUnderline?: boolean;
7+
withArrow?: boolean;
8+
animationOnHover?: boolean;
9+
fontSize?: BoxProps['fontSize'];
10+
};

src/components/Button/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './LinkButton';

src/components/Cursor/Cursor.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,40 @@
11
import { motion } from 'framer-motion';
22
import { useRef } from 'react';
33
import { useSpringMousePosition } from '../../hooks';
4+
import { useCursor } from './CursorProvider';
45

56
const FollowCursor = () => {
7+
const { insets } = useCursor();
68
const ref = useRef<HTMLDivElement>(null);
79
const { x, y } = useSpringMousePosition(ref);
810

11+
const { width, height, top, left } = insets ?? {
12+
width: 56,
13+
height: 56,
14+
top: 0,
15+
left: 0,
16+
};
17+
const props = {
18+
boxShadow: '0 0 50px 10px violet',
19+
width: width,
20+
height: height,
21+
transition: !insets ? 'all 0.0s ease' : 'all 0.5s ease',
22+
borderRadius: insets ? '5px' : '50%',
23+
};
24+
925
return (
1026
<motion.div
27+
className="cursor"
1128
ref={ref}
1229
style={{
13-
x,
14-
y,
30+
x: top === 0 ? x : left,
31+
y: top === 0 ? y : top,
1532
backgroundColor: '#fff',
1633
mixBlendMode: 'difference',
17-
boxShadow: '0 0 50px 10px violet',
1834
zIndex: 999,
19-
width: '56px',
20-
height: '56px',
21-
borderRadius: '50%',
35+
position: 'fixed',
36+
pointerEvents: 'none',
37+
...props,
2238
}}
2339
/>
2440
);
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { createContext, useContext, useState } from 'react';
2+
import FollowCursor from './Cursor';
3+
4+
export const CursorContext = createContext<{
5+
cursorState: boolean;
6+
insets:
7+
| {
8+
height: number;
9+
width: number;
10+
top: number;
11+
left: number;
12+
}
13+
| undefined;
14+
toggle: () => void;
15+
setCursorInsets: (
16+
args:
17+
| {
18+
height: number;
19+
width: number;
20+
top: number;
21+
left: number;
22+
}
23+
| undefined,
24+
) => void;
25+
}>({
26+
cursorState: true,
27+
insets: undefined,
28+
// eslint-disable-next-line @typescript-eslint/no-empty-function
29+
toggle: () => {},
30+
setCursorInsets: (
31+
args:
32+
| {
33+
height: number;
34+
width: number;
35+
top: number;
36+
left: number;
37+
}
38+
| undefined,
39+
// eslint-disable-next-line @typescript-eslint/no-empty-function
40+
) => {},
41+
});
42+
43+
const CursorProvider = ({ children }: { children: React.ReactNode }) => {
44+
const [cursorState, setCursorState] = useState(true);
45+
const [insets, setCursorSize] = useState<
46+
| {
47+
height: number;
48+
width: number;
49+
top: number;
50+
left: number;
51+
}
52+
| undefined
53+
>(undefined);
54+
55+
const toggle = () => {
56+
setCursorState((prev) => !prev);
57+
};
58+
59+
const setCursorInsets = (
60+
args:
61+
| {
62+
height: number;
63+
width: number;
64+
top: number;
65+
left: number;
66+
}
67+
| undefined,
68+
) => {
69+
setCursorSize(args);
70+
};
71+
72+
return (
73+
<CursorContext.Provider
74+
value={{ cursorState, insets, toggle, setCursorInsets }}
75+
>
76+
<FollowCursor />
77+
{children}
78+
</CursorContext.Provider>
79+
);
80+
};
81+
82+
export const useCursor = () => {
83+
const context = useContext(CursorContext);
84+
if (context === undefined) {
85+
throw new Error('useCursor must be used within a CursorProvider');
86+
}
87+
return context;
88+
};
89+
90+
export default CursorProvider;

0 commit comments

Comments
 (0)