Header.tsx 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. 'use client';
  2. import { Fragment, MutableRefObject, PropsWithChildren, RefObject, useCallback, useEffect, useRef, useState } from 'react';
  3. import { Dialog, Popover } from '@headlessui/react';
  4. import clsx from 'clsx';
  5. import { banner, blogEnabled, componentsRounded, iconsCountRounded, sponsorsUrl, uiGithubUrl } from '@/config/site';
  6. import Icon from '@/components/Icon';
  7. import GoToTop from '@/components/layout/GoToTop';
  8. import Link from '@/components/Link';
  9. import NavLink from '@/components/NavLink';
  10. import Shape from '@/components/Shape';
  11. import { usePathname } from 'next/navigation';
  12. const NavDropdown = ({ title, children, active, footer = false }) => {
  13. return (
  14. <Popover className="navbar-dropdown">
  15. {({ open }) => (
  16. <>
  17. <Popover.Button className={clsx('navbar-link', active && 'active')}>{title}</Popover.Button>
  18. <Popover.Panel className="navbar-dropdown-menu">
  19. <div className="navbar-dropdown-menu-content">{children}</div>
  20. {footer && <div className="navbar-dropdown-menu-footer">{footer}</div>}
  21. </Popover.Panel>
  22. </>
  23. )}
  24. </Popover>
  25. );
  26. };
  27. const menuLinks = [
  28. {
  29. title: 'UI Kit',
  30. menu: 'ui',
  31. children: [
  32. {
  33. icon: 'home',
  34. href: '/',
  35. title: 'About',
  36. description: 'Develop beautiful web apps with Tabler',
  37. },
  38. {
  39. icon: 'layout-dashboard',
  40. href: '/preview',
  41. title: 'Preview template',
  42. description: 'See what Tabler looks like and offers',
  43. },
  44. {
  45. icon: 'script',
  46. href: '/docs',
  47. title: 'Documentation',
  48. description: 'Read how to develop apps with Tabler',
  49. },
  50. {
  51. icon: 'lego',
  52. href: '/features',
  53. title: 'Features',
  54. description: 'See what kind of features you can find here',
  55. },
  56. {
  57. icon: 'lifebuoy',
  58. href: '/support',
  59. title: 'Support',
  60. description: 'Write to us if you need anything!',
  61. },
  62. {
  63. icon: 'brand-github',
  64. href: uiGithubUrl,
  65. title: 'Source code',
  66. description: 'View Tabler\'s source code ',
  67. props: {
  68. target: '_blank',
  69. rel: 'nofollow',
  70. },
  71. },
  72. ],
  73. },
  74. {
  75. href: '/emails',
  76. menu: 'emails',
  77. title: 'Email templates',
  78. },
  79. {
  80. href: '/icons',
  81. menu: 'icons',
  82. title: (
  83. <>
  84. <span className="d-none lg:d-inline">Over {iconsCountRounded} </span>
  85. Icons
  86. </>
  87. ),
  88. },
  89. ...(blogEnabled ? [{
  90. href: '/blog',
  91. menu: 'blog',
  92. title: <>Blog</>,
  93. }] : []),
  94. {
  95. href: '/docs',
  96. menu: 'docs',
  97. title: 'Documentation',
  98. },
  99. // {
  100. // href: '/guides',
  101. // menu: 'guides',
  102. // title: 'Guides',
  103. // },
  104. {
  105. menu: 'sponsors',
  106. href: sponsorsUrl,
  107. type: 'button',
  108. title: (
  109. <span>
  110. Sponsor<span className="d-none lg:d-inline"> project</span>
  111. </span>
  112. ),
  113. icon: <Icon name="heart" filled color="red" />,
  114. },
  115. ];
  116. const NavbarLink = (link, menu) => {
  117. // const router = useRouter()
  118. if (link.type === 'button') {
  119. return (
  120. <div className="navbar-item">
  121. <a href={link.href} className="btn" target="_blank" rel="noopener noreferrer">
  122. {link.icon}
  123. {link.title}
  124. </a>
  125. </div>
  126. );
  127. } else if (link.children) {
  128. return (
  129. <NavDropdown title={link.title} active={menu === link.menu}>
  130. {link.children.map((link) => (
  131. <Popover.Button as={Link} href={link.href || ''} className="navbar-dropdown-menu-link" key={link.title} onClick={() => true} {...link.props}>
  132. <div className="row g-3">
  133. <div className="col-auto">
  134. <Shape icon={link.icon} />
  135. </div>
  136. <div className="col">
  137. <h5 className="mb-1">{link.title}</h5>
  138. <p className="font-h6 m-0 text-muted">{link.description}</p>
  139. </div>
  140. </div>
  141. </Popover.Button>
  142. ))}
  143. </NavDropdown>
  144. );
  145. }
  146. return (
  147. // router.pathname.replace(/^\//, '').startsWith(link.menu)
  148. <NavLink href={link.href} className="navbar-link" active={false}>
  149. {link.title}
  150. </NavLink>
  151. );
  152. };
  153. const SidebarLink = (link, menu, onClick) => {
  154. if (link.type === 'button') {
  155. return (
  156. <div className="aside-menu-item mt-4">
  157. <a href={link.href} className="btn btn-block" target="_blank" rel="noopener noreferrer" onClick={onClick}>
  158. {link.icon}
  159. {link.title}
  160. </a>
  161. </div>
  162. );
  163. } else if (link.children) {
  164. return (
  165. <div className="aside-menu-item">
  166. <div className={clsx('aside-menu-title', { active: menu === link.menu })}>{link.title}</div>
  167. <div className="aside-menu-children">
  168. {link.children.map((link) => (
  169. <Link href={link.href || ''} key={link.title} className="aside-menu-link" onClick={onClick} {...link.props}>
  170. {link.title}
  171. </Link>
  172. ))}
  173. </div>
  174. </div>
  175. );
  176. }
  177. return (
  178. <Link href={link.href} className={clsx('aside-menu-link', { active: menu === link.menu })} onClick={onClick}>
  179. {link.title}
  180. </Link>
  181. );
  182. };
  183. const Navbar = ({ menu, opened, onClick, ...props }: { menu?: string; opened?: boolean; onClick?: (event: React.MouseEvent) => void; className?: string }) => {
  184. return <div className={clsx('navbar', opened && 'opened', props.className)}>{menuLinks.map((link) => (<Fragment key={link.menu}>{NavbarLink(link, menu)}</Fragment>))}</div>;
  185. };
  186. const Banner = () => {
  187. const [showBanner, setShowBanner] = useState(false);
  188. useEffect(() => {
  189. if (window.localStorage.getItem(`banner-${banner.id}`) !== '1') {
  190. setShowBanner(true);
  191. }
  192. }, []);
  193. function closeBanner() {
  194. localStorage.setItem(`banner-${banner.id}`, '1');
  195. setShowBanner(false);
  196. }
  197. return (
  198. banner.show &&
  199. showBanner && (
  200. <div className="banner">
  201. <div className="container">
  202. <div className="text-truncate">{banner.text}</div>
  203. <a href={banner.link.href} className="ml-5 banner-link" target="_blank">
  204. {banner.link.text}
  205. </a>
  206. </div>
  207. <a onClick={closeBanner} className="banner-close">
  208. <Icon name="x" />
  209. </a>
  210. </div>
  211. )
  212. );
  213. };
  214. export default function Header({ headerStatic, className, pageProps, ...props }: { headerStatic?: boolean; className?: string; pageProps?: any }) {
  215. const [sticky, setSticky] = useState(false);
  216. const [isOpen, setIsOpen] = useState(false);
  217. const pathname = usePathname();
  218. function closeModal() {
  219. setIsOpen(false);
  220. }
  221. function toggleModal() {
  222. setIsOpen(!isOpen);
  223. }
  224. useEffect(() => {
  225. const handleScroll = () => {
  226. setSticky(window.pageYOffset > 0);
  227. };
  228. window.addEventListener('scroll', handleScroll);
  229. handleScroll();
  230. return () => {
  231. window.removeEventListener('scroll', handleScroll);
  232. };
  233. }, []);
  234. return (
  235. <>
  236. <Banner />
  237. <header
  238. className={clsx(
  239. 'header',
  240. sticky && 'header-sticky',
  241. pathname.startsWith('/docs') && 'header-docs',
  242. className,
  243. )}
  244. >
  245. <div className="container">
  246. <nav className="row items-center">
  247. <div className="col-auto">
  248. <Link href="/" className={clsx('logo' /*, pageProps.brand ? `logo-${pageProps.brand}` : ''*/)} aria-label="Tabler" />
  249. </div>
  250. <div className="col-auto ml-auto">
  251. <div className="d-none md:d-block">
  252. {/* <Navbar menu={pageProps.menu} /> */}
  253. <Navbar />
  254. </div>
  255. <div className="md:d-none">
  256. <button
  257. className={clsx('navbar-toggle', {
  258. active: isOpen,
  259. })}
  260. onClick={toggleModal}
  261. >
  262. <span />
  263. <span />
  264. <span />
  265. <span />
  266. </button>
  267. </div>
  268. </div>
  269. </nav>
  270. </div>
  271. </header>
  272. <Dialog open={isOpen} onClose={closeModal} className="modal-backdrop">
  273. <Dialog.Panel className="modal modal-side">
  274. <div className={clsx('aside-menu mt-4')}>
  275. {/* {menuLinks.map((link) => (
  276. // <Fragment key={link.menu}>{SidebarLink(link, pageProps.menu, closeModal)}</Fragment>
  277. ))} */}
  278. </div>
  279. </Dialog.Panel>
  280. </Dialog>
  281. <GoToTop />
  282. </>
  283. );
  284. }