diff --git a/package-lock.json b/package-lock.json index 484ef09..0adda67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2203,15 +2203,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "license": "MIT", - "dependencies": { - "node-fetch": "^2.6.12" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3420,63 +3411,6 @@ "node": ">= 0.4" } }, - "node_modules/html-parse-stringify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", - "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", - "dependencies": { - "void-elements": "3.1.0" - } - }, - "node_modules/i18next": { - "version": "25.2.1", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.2.1.tgz", - "integrity": "sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw==", - "funding": [ - { - "type": "individual", - "url": "https://locize.com" - }, - { - "type": "individual", - "url": "https://locize.com/i18next.html" - }, - { - "type": "individual", - "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" - } - ], - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.27.1" - }, - "peerDependencies": { - "typescript": "^5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/i18next-browser-languagedetector": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.1.0.tgz", - "integrity": "sha512-mHZxNx1Lq09xt5kCauZ/4bsXOEA2pfpwSoU11/QTJB+pD94iONFwp+ohqi///PwiFvjFOxe1akYCdHyFo1ng5Q==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.2" - } - }, - "node_modules/i18next-http-backend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.2.tgz", - "integrity": "sha512-PdlvPnvIp4E1sYi46Ik4tBYh/v/NbYfFFgTjkwFl0is8A18s7/bx9aXqsrOax9WUbeNS6mD2oix7Z0yGGf6m5g==", - "license": "MIT", - "dependencies": { - "cross-fetch": "4.0.0" - } - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4529,26 +4463,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4991,32 +4905,6 @@ "react": "^19.1.0" } }, - "node_modules/react-i18next": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.2.tgz", - "integrity": "sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.25.0", - "html-parse-stringify": "^3.0.1" - }, - "peerDependencies": { - "i18next": ">= 23.2.3", - "react": ">= 16.8.0", - "typescript": "^5" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -5704,12 +5592,6 @@ "node": ">=8.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -5903,30 +5785,6 @@ "punycode": "^2.1.0" } }, - "node_modules/void-elements": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", - "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/src/assets/constants/socials.ts b/src/assets/constants/socials.ts index 09a99c6..472ef58 100644 --- a/src/assets/constants/socials.ts +++ b/src/assets/constants/socials.ts @@ -1,6 +1,9 @@ + + export default [ { title: 'instagram', link: 'https://www.instagram.com/yourprofile', + icon: 'instagram', }, ]; diff --git a/src/components/layout/footer/FooterInfo.tsx b/src/components/layout/footer/FooterInfo.tsx index f3b7cd8..b2e2b46 100644 --- a/src/components/layout/footer/FooterInfo.tsx +++ b/src/components/layout/footer/FooterInfo.tsx @@ -1,5 +1,6 @@ import { appConstants } from '@/assets/constants'; import images from '@/assets/images/images'; +import { Icon, IconName } from '@/components/uikit/icons'; import { t } from '@/locales/translates'; import Image from 'next/image'; import Link from 'next/link'; @@ -31,12 +32,7 @@ export default function FooterInfo({ key={social.title} className="text-white lg:flex-1" > - {/* {social.title} */} - {social.title} + ))} diff --git a/src/components/layout/hamburgerMenu/index.tsx b/src/components/layout/hamburgerMenu/index.tsx new file mode 100644 index 0000000..4909aee --- /dev/null +++ b/src/components/layout/hamburgerMenu/index.tsx @@ -0,0 +1,91 @@ +'use client'; + +import Link from 'next/link'; +import Image from 'next/image'; +import images from '@/assets/images/images'; +import { usePathname } from 'next/navigation'; +import { Icon } from '@/components/uikit/icons'; +import { t } from '@/locales/translates'; +import { useEffect, useRef } from 'react'; +import FooterInfo from '../footer/FooterInfo'; +import routeFactory from '@/assets/constants/routeFactory'; + +interface Props { + isOpen: boolean; + onClose: () => void; +} + +const links = [routeFactory.home, routeFactory.about, routeFactory.contact]; + +export default function HamburgerMenu({ isOpen, onClose }: Props) { + const pathname = usePathname(); + const menuRef = useRef(null); + + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + onClose(); + } + } + + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + document.getElementsByTagName('main')[0].style.overflow = 'hidden'; + } else { + document.getElementsByTagName('main')[0].style.overflow = 'auto'; + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [isOpen, onClose]); + + if (!isOpen) return null; + return ( +
+
+ +
+
+ + logo + + +
+ + + +
+ +
+
+
+ ); +} diff --git a/src/components/layout/header/index.tsx b/src/components/layout/header/index.tsx index 5d78152..6167453 100644 --- a/src/components/layout/header/index.tsx +++ b/src/components/layout/header/index.tsx @@ -1,47 +1,53 @@ import images from '@/assets/images/images'; import Navbar from '../navbar'; -import Button from '@/components/uikit/button'; +import Button, { IconButton } from '@/components/uikit/button'; import { t } from '@/locales/translates'; +import { Icon } from '@/components/uikit/icons'; export default function Header() { + const bannerFooter = [ + t('com_home_Banner_link1') as string, + t('com_home_Banner_link2') as string, + t('com_home_Banner_link3') as string, + ]; return (
-
+
-
-
-

+
+
+

{t('com_home_banner_title') as string}

-

+

{t('com_home_banner_title_bold') as string}

-
-

diff --git a/src/components/layout/navbar/index.tsx b/src/components/layout/navbar/index.tsx index 207efea..a36df57 100644 --- a/src/components/layout/navbar/index.tsx +++ b/src/components/layout/navbar/index.tsx @@ -1,11 +1,99 @@ +// 'use client'; + +// import images from '@/assets/images/images'; +// import { Icon } from '@/components/uikit/icons'; +// import { t } from '@/locales/translates'; +// import Image from 'next/image'; +// import Link from 'next/link'; +// import { usePathname } from 'next/navigation'; +// import { useState } from 'react'; + +// export default function Navbar() { +// const [open, setOpen] = useState(false); +// const pathname = usePathname(); + +// const links = [ +// { href: '/', label: t('pages_home_title') as string }, +// { href: '/about', label: t('pages_about_title') as string }, +// { href: '/blog', label: t('pages_contact_title') as string }, +// { href: '/contact', label: t('pages_contact_title') as string }, +// { href: '/product', label: t('pages_product_title') as string }, +// { href: '/project', label: t('pages_project_title') as string }, +// ]; + +// const linkClass = (href: string) => +// pathname === href ? ' text-red-500 font-semibold' : 'text-white'; + +// return ( +// +// ); +// } + 'use client'; import images from '@/assets/images/images'; -import { t } from '@/locales/translates'; +import { Icon } from '@/components/uikit/icons'; import Image from 'next/image'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; import { useState } from 'react'; +import HamburgerMenu from '../hamburgerMenu'; +import { t } from '@/locales/translates'; export default function Navbar() { const [open, setOpen] = useState(false); @@ -19,58 +107,52 @@ export default function Navbar() { { href: '/product', label: t('pages_product_title') as string }, { href: '/project', label: t('pages_project_title') as string }, ]; - const linkClass = (href: string) => - pathname === href ? ' text-red-500 font-semibold' : 'text-white'; + pathname === href ? 'text-primary font-semibold' : 'text-white'; return ( - + + setOpen(false)} /> + ); } diff --git a/src/components/uikit/button/button.d.ts b/src/components/uikit/button/button.d.ts new file mode 100644 index 0000000..72dc437 --- /dev/null +++ b/src/components/uikit/button/button.d.ts @@ -0,0 +1,14 @@ +export interface IBaseButtonProps extends ButtonHTMLAttributes { + children: ReactNode; + loading?: boolean; + disabled?: boolean; + className?: string +} + +export interface ITextButtonProps extends IBaseButtonProps{ + endIcon?: ReactNode; +} +export interface IIconButtonProps extends IBaseButtonProps{ + color?: 'primary' | 'secondary' + size?: 'small' | 'medium' | 'large' +} \ No newline at end of file diff --git a/src/components/uikit/button/index.tsx b/src/components/uikit/button/index.tsx index e6cdb64..aa6ebc9 100644 --- a/src/components/uikit/button/index.tsx +++ b/src/components/uikit/button/index.tsx @@ -1,36 +1,24 @@ 'use client'; -import React, { ButtonHTMLAttributes, ReactNode } from 'react'; +import React from 'react'; +import { IIconButtonProps, ITextButtonProps } from './button'; -interface ButtonProps extends ButtonHTMLAttributes { - children?: ReactNode; - loading?: boolean; - disabled?: boolean; - endIcon?: ReactNode; - size?: 'small' | 'medium' | 'large'; - variant?: 'solid' | 'text'; -} - -const Button: React.FC = ({ +const Button: React.FC = ({ children, loading = false, disabled = false, endIcon, - size = 'medium', - variant = 'solid', className, ...props }) => { - - return ( ); }; export default Button; + +export const IconButton: React.FC = ({ + children, + loading = false, + disabled = false, + size = 'medium', + color = 'primary', + className, + ...props +}) => { + const colorClass = + color === 'primary' ? 'bg-primary text-white' : 'bg-gray-300 text-gray-500'; + + const sizeClass = + size === 'large' ? 'w-12 h-12' : size === 'small' ? 'w-8 h-8' : 'w-10 h-10'; + + return ( + + ); +}; diff --git a/src/components/uikit/icons/achievementIcon.tsx b/src/components/uikit/icons/achievementIcon.tsx new file mode 100644 index 0000000..03aa88f --- /dev/null +++ b/src/components/uikit/icons/achievementIcon.tsx @@ -0,0 +1,22 @@ +export const AchievementIcon = (props: React.SVGProps) => ( + + + + +); diff --git a/src/components/uikit/icons/closeIcon.tsx b/src/components/uikit/icons/closeIcon.tsx new file mode 100644 index 0000000..a092b3a --- /dev/null +++ b/src/components/uikit/icons/closeIcon.tsx @@ -0,0 +1,15 @@ +export const CloseIcon = (props: React.SVGProps) => ( + + + +); diff --git a/src/components/uikit/icons/index.tsx b/src/components/uikit/icons/index.tsx new file mode 100644 index 0000000..eec8384 --- /dev/null +++ b/src/components/uikit/icons/index.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { CloseIcon } from './closeIcon'; +import { MenuIcon } from './menuIcon'; +import { SettingIcon } from './settingIcon'; +import { AchievementIcon } from './achievementIcon'; +import { PhoneIcon } from './phoneIcon'; +import { InstagramIcon } from './instagramIcon'; + +export const icons = { + close: CloseIcon, + menu: MenuIcon, + setting: SettingIcon, + achievement: AchievementIcon, + phone: PhoneIcon, + instagram: InstagramIcon, +}; + +export type IconName = keyof typeof icons; + +interface IconProps extends React.SVGProps { + name: IconName; +} + +export const Icon = ({ name, ...props }: IconProps) => { + const Component = icons[name]; + if (!Component) return null; + return ; +}; diff --git a/src/components/uikit/icons/instagramIcon.tsx b/src/components/uikit/icons/instagramIcon.tsx new file mode 100644 index 0000000..13f63a0 --- /dev/null +++ b/src/components/uikit/icons/instagramIcon.tsx @@ -0,0 +1,27 @@ +export const InstagramIcon = (props: React.SVGProps) => ( + + + + + + + + + + +); diff --git a/src/components/uikit/icons/menuIcon.tsx b/src/components/uikit/icons/menuIcon.tsx new file mode 100644 index 0000000..86a56a0 --- /dev/null +++ b/src/components/uikit/icons/menuIcon.tsx @@ -0,0 +1,15 @@ +export const MenuIcon = (props: React.SVGProps) => ( + + + +); diff --git a/src/components/uikit/icons/phoneIcon.tsx b/src/components/uikit/icons/phoneIcon.tsx new file mode 100644 index 0000000..e0a2968 --- /dev/null +++ b/src/components/uikit/icons/phoneIcon.tsx @@ -0,0 +1,38 @@ +export const PhoneIcon = (props: React.SVGProps) => ( + + + + + + + + + + + + +); diff --git a/src/components/uikit/icons/settingIcon.tsx b/src/components/uikit/icons/settingIcon.tsx new file mode 100644 index 0000000..7c2d8e9 --- /dev/null +++ b/src/components/uikit/icons/settingIcon.tsx @@ -0,0 +1,27 @@ +export const SettingIcon = (props: React.SVGProps) => ( + + + + +);