Header.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. 'use client';
  2. import {
  3. Fragment,
  4. useEffect,
  5. useState,
  6. } from 'react';
  7. import { Dialog, Popover } from '@headlessui/react';
  8. import clsx from 'clsx';
  9. import { banner,
  10. blogEnabled,
  11. iconsCountRounded,
  12. sponsorsUrl,
  13. uiGithubUrl,
  14. } from '@/config/site';
  15. import Icon from '@/components/Icon';
  16. import GoToTop from '@/components/layout/GoToTop';
  17. import Link from '@/components/Link';
  18. import NavLink from '@/components/NavLink';
  19. import Shape from '@/components/Shape';
  20. import { usePathname } from 'next/navigation';
  21. import { signOut, useSession } from 'next-auth/react';
  22. import { useRouter } from 'next/navigation';
  23. const NavDropdown = ({ title, children, active, footer = false }) => {
  24. return (
  25. <Popover className="navbar-dropdown">
  26. {({ open }) => (
  27. <>
  28. <Popover.Button className={clsx('navbar-link', active && 'active')}>{title}</Popover.Button>
  29. <Popover.Panel className="navbar-dropdown-menu">
  30. <div className="navbar-dropdown-menu-content">{children}</div>
  31. {footer && <div className="navbar-dropdown-menu-footer">{footer}</div>}
  32. </Popover.Panel>
  33. </>
  34. )}
  35. </Popover>
  36. );
  37. };
  38. const menuLinks = [
  39. {
  40. title: 'UI Kit',
  41. menu: 'ui',
  42. children: [
  43. {
  44. icon: 'home',
  45. href: '/',
  46. title: 'About',
  47. description: 'Develop beautiful web apps with Tabler',
  48. },
  49. {
  50. icon: 'layout-dashboard',
  51. href: '/preview',
  52. title: 'Preview template',
  53. description: 'See what Tabler looks like and offers',
  54. },
  55. {
  56. icon: 'script',
  57. href: '/docs',
  58. title: 'Documentation',
  59. description: 'Read how to develop apps with Tabler',
  60. },
  61. {
  62. icon: 'lego',
  63. href: '/features',
  64. title: 'Features',
  65. description: 'See what kind of features you can find here',
  66. },
  67. {
  68. icon: 'lifebuoy',
  69. href: '/support',
  70. title: 'Support',
  71. description: 'Write to us if you need anything!',
  72. },
  73. {
  74. icon: 'brand-github',
  75. href: uiGithubUrl,
  76. title: 'Source code',
  77. description: 'View Tabler\'s source code ',
  78. props: {
  79. target: '_blank',
  80. rel: 'nofollow',
  81. },
  82. },
  83. ],
  84. },
  85. {
  86. href: '/emails',
  87. menu: 'emails',
  88. title: 'Email templates',
  89. },
  90. {
  91. href: '/icons',
  92. menu: 'icons',
  93. title: (
  94. <>
  95. <span className="d-none lg:d-inline">Over {iconsCountRounded} </span>
  96. Icons
  97. </>
  98. ),
  99. },
  100. ...(blogEnabled ? [{
  101. href: '/blog',
  102. menu: 'blog',
  103. title: <>Blog</>,
  104. }] : []),
  105. {
  106. href: '/docs',
  107. menu: 'docs',
  108. title: 'Documentation',
  109. },
  110. // {
  111. // href: '/guides',
  112. // menu: 'guides',
  113. // title: 'Guides',
  114. // },
  115. {
  116. menu: 'sponsors',
  117. href: sponsorsUrl,
  118. type: 'button',
  119. title: (
  120. <span>
  121. Sponsor<span className="d-none lg:d-inline"> project</span>
  122. </span>
  123. ),
  124. icon: <Icon name="heart" filled color="red" />,
  125. },
  126. ];
  127. const NavbarLink = (link, menu) => {
  128. // const router = useRouter()
  129. if (link.type === 'button') {
  130. return (
  131. <div className="navbar-item">
  132. <a href={link.href} className="btn" target="_blank" rel="noopener noreferrer">
  133. {link.icon}
  134. {link.title}
  135. </a>
  136. </div>
  137. );
  138. } else if (link.children) {
  139. return (
  140. <NavDropdown title={link.title} active={menu === link.menu}>
  141. {link.children.map((link) => (
  142. <Popover.Button as={Link} href={link.href || ''} className="navbar-dropdown-menu-link" key={link.title} onClick={() => true} {...link.props}>
  143. <div className="row g-3">
  144. <div className="col-auto">
  145. <Shape icon={link.icon} />
  146. </div>
  147. <div className="col">
  148. <h5 className="mb-1">{link.title}</h5>
  149. <p className="font-h6 m-0 text-muted">{link.description}</p>
  150. </div>
  151. </div>
  152. </Popover.Button>
  153. ))}
  154. </NavDropdown>
  155. );
  156. }
  157. return (
  158. // router.pathname.replace(/^\//, '').startsWith(link.menu)
  159. <NavLink href={link.href} className="navbar-link">
  160. {link.title}
  161. </NavLink>
  162. );
  163. };
  164. const SidebarLink = (link, menu, onClick) => {
  165. if (link.type === 'button') {
  166. return (
  167. <div className="aside-menu-item mt-4">
  168. <a href={link.href} className="btn btn-block" target="_blank" rel="noopener noreferrer" onClick={onClick}>
  169. {link.icon}
  170. {link.title}
  171. </a>
  172. </div>
  173. );
  174. } else if (link.children) {
  175. return (
  176. <div className="aside-menu-item">
  177. <div className={clsx('aside-menu-title', { active: menu === link.menu })}>{link.title}</div>
  178. <div className="aside-menu-children">
  179. {link.children.map((link) => (
  180. <Link href={link.href || ''} key={link.title} className="aside-menu-link" onClick={onClick} {...link.props}>
  181. {link.title}
  182. </Link>
  183. ))}
  184. </div>
  185. </div>
  186. );
  187. }
  188. return (
  189. <Link href={link.href} className={clsx('aside-menu-link', { active: menu === link.menu })} onClick={onClick}>
  190. {link.title}
  191. </Link>
  192. );
  193. };
  194. const NavigationAuth = () => {
  195. const { data: session, status } = useSession();
  196. const router = useRouter();
  197. const image = session?.user?.image;
  198. const name = session?.user?.name;
  199. const email = session?.user?.email;
  200. const signIn = () => {
  201. if (status === 'loading') return;
  202. router.push('/api/auth/signin');
  203. };
  204. return <div className="navbar-item d-flex items-center">
  205. {
  206. !session &&
  207. <a onClick={() => signIn()} className={clsx('btn', { disabled: status === 'loading'})}>
  208. Log in
  209. </a>
  210. }
  211. {
  212. session &&
  213. <Popover className="navbar-dropdown">
  214. {({ open }) => (
  215. <>
  216. <Popover.Button className={clsx('navbar-link d-flex items-center lh-1 text-reset p-0')}>
  217. {
  218. image
  219. ? <span
  220. className="avatar avatar"
  221. style={{
  222. backgroundImage: `url(${image})`,
  223. }}
  224. />
  225. : <span className="avatar avatar text-center">
  226. {name ? name.toUpperCase().substring(0, 1) : 'T'}
  227. </span>
  228. }
  229. <div className="pl-2">
  230. <small className="d-block">{name}</small>
  231. {
  232. email &&
  233. <small className="mt-1 small text-muted">{session.user?.email}</small>
  234. }
  235. </div>
  236. </Popover.Button>
  237. <Popover.Panel className="navbar-dropdown-menu">
  238. <div className="navbar-dropdown-menu-content">
  239. <div onClick={() => router.push('billing')} className="navbar-dropdown-menu-link">
  240. <div className="row items-center g-3">
  241. <div className="col-auto">
  242. <Shape icon='rocket'/>
  243. </div>
  244. <div className="col">
  245. <h5>Billing</h5>
  246. </div>
  247. </div>
  248. </div>
  249. <div onClick={() => signOut()} className="navbar-dropdown-menu-link">
  250. <div className="row items-center g-3">
  251. <div className="col-auto">
  252. <Shape icon='logout'/>
  253. </div>
  254. <div className="col">
  255. <h5>Log out</h5>
  256. </div>
  257. </div>
  258. </div>
  259. </div>
  260. </Popover.Panel>
  261. </>
  262. )}
  263. </Popover>
  264. }
  265. </div>;
  266. };
  267. const Navbar = ({
  268. menu,
  269. opened,
  270. onClick,
  271. ...props
  272. }: {
  273. menu?: string;
  274. opened?: boolean;
  275. onClick?: (event: React.MouseEvent) => void;
  276. className?: string
  277. }) => {
  278. return <div className={clsx('navbar', opened && 'opened', props.className)}>
  279. {menuLinks.map((link) => (<Fragment key={link.menu}>{NavbarLink(link, menu)}</Fragment>))}
  280. <NavigationAuth/>
  281. </div>;
  282. };
  283. const Banner = () => {
  284. const [showBanner, setShowBanner] = useState(false);
  285. useEffect(() => {
  286. if (window.localStorage.getItem(`banner-${banner.id}`) !== '1') {
  287. setShowBanner(true);
  288. }
  289. }, []);
  290. function closeBanner() {
  291. localStorage.setItem(`banner-${banner.id}`, '1');
  292. setShowBanner(false);
  293. }
  294. return (
  295. banner.show &&
  296. showBanner && (
  297. <div className="banner">
  298. <div className="container">
  299. <div className="text-truncate">{banner.text}</div>
  300. <a href={banner.link.href} className="ml-5 banner-link" target="_blank">
  301. {banner.link.text}
  302. </a>
  303. </div>
  304. <a onClick={closeBanner} className="banner-close">
  305. <Icon name="x" />
  306. </a>
  307. </div>
  308. )
  309. );
  310. };
  311. export default function Header({ headerStatic, className, pageProps, ...props }: { headerStatic?: boolean; className?: string; pageProps?: any }) {
  312. const [sticky, setSticky] = useState(false);
  313. const [isOpen, setIsOpen] = useState(false);
  314. const pathname = usePathname();
  315. function closeModal() {
  316. setIsOpen(false);
  317. }
  318. function toggleModal() {
  319. setIsOpen(!isOpen);
  320. }
  321. useEffect(() => {
  322. const handleScroll = () => {
  323. setSticky(window.pageYOffset > 0);
  324. };
  325. window.addEventListener('scroll', handleScroll);
  326. handleScroll();
  327. return () => {
  328. window.removeEventListener('scroll', handleScroll);
  329. };
  330. }, []);
  331. return (
  332. <>
  333. <Banner />
  334. <header
  335. className={clsx(
  336. 'header',
  337. sticky && 'header-sticky',
  338. pathname.startsWith('/docs') && 'header-docs',
  339. className,
  340. )}
  341. >
  342. <div className="container">
  343. <nav className="row items-center">
  344. <div className="col-auto">
  345. <Link href="/" className={clsx('logo' /*, pageProps.brand ? `logo-${pageProps.brand}` : ''*/)} aria-label="Tabler" />
  346. </div>
  347. <div className="col-auto ml-auto">
  348. <div className="d-none md:d-block">
  349. {/* <Navbar menu={pageProps.menu} /> */}
  350. <Navbar />
  351. </div>
  352. <div className="md:d-none">
  353. <button
  354. className={clsx('navbar-toggle', {
  355. active: isOpen,
  356. })}
  357. onClick={toggleModal}
  358. >
  359. <span />
  360. <span />
  361. <span />
  362. <span />
  363. </button>
  364. </div>
  365. </div>
  366. </nav>
  367. </div>
  368. </header>
  369. <Dialog open={isOpen} onClose={closeModal} className="modal-backdrop">
  370. <Dialog.Panel className="modal modal-side">
  371. <div className={clsx('aside-menu mt-4')}>
  372. {/* {menuLinks.map((link) => (
  373. // <Fragment key={link.menu}>{SidebarLink(link, pageProps.menu, closeModal)}</Fragment>
  374. ))} */}
  375. </div>
  376. </Dialog.Panel>
  377. </Dialog>
  378. <GoToTop />
  379. </>
  380. );
  381. }