From 0c3ae98822f32f7314984f5ebce405035ed43d4f Mon Sep 17 00:00:00 2001 From: Evan Jacobs Date: Sat, 27 May 2023 01:28:36 -0400 Subject: [PATCH 1/7] refactor: bunch of stuff, upgrade algolia --- ...nies-manifest.js => companies-manifest.tsx | 2 + components/{Label.js => Label.tsx} | 2 +- components/{Layout.ts => Layout.tsx} | 2 +- components/Link.js | 58 - components/Link.tsx | 78 + components/Nav/{Logo.js => Logo.tsx} | 2 +- .../Nav/{MobileNavbar.js => MobileNavbar.tsx} | 64 +- .../Nav/{NavButton.js => NavButton.tsx} | 5 +- components/Nav/{NavLinks.js => NavLinks.tsx} | 4 +- components/Nav/{Navbar.js => Navbar.tsx} | 136 +- components/Nav/Search.js | 179 -- components/Nav/SearchWithAlgolia.js | 30 - components/Nav/SearchWithAlgolia.tsx | 25 + .../Nav/{SidebarMenus.js => SidebarMenus.tsx} | 86 +- components/Nav/{Social.js => Social.tsx} | 12 +- components/Nav/{index.js => index.tsx} | 17 +- components/SeoHead.js | 2 +- components/UsersLogos/{index.js => index.tsx} | 13 +- .../{WithIsScrolled.js => WithIsScrolled.tsx} | 6 +- global.d.ts | 11 + next.config.mjs | 2 +- package.json | 6 +- pages/_app.js | 28 - pages/{_document.js => _app.tsx} | 78 +- pages/_document.tsx | 43 + pages/{index.js => index.tsx} | 162 +- .../{Link.spec.js => Link.spec.tsx} | 10 +- test/components/NavBar/Search.spec.js | 9 - .../__snapshots__/MobileNavbar.spec.js.snap | 197 +-- .../__snapshots__/NavButton.spec.js.snap | 2 +- .../__snapshots__/NavLinks.spec.js.snap | 6 +- .../NavBar/__snapshots__/Navbar.spec.js.snap | 667 +++----- .../NavBar/__snapshots__/Search.spec.js.snap | 116 -- .../NavBar/__snapshots__/Sidebar.spec.js.snap | 2 +- .../__snapshots__/SidebarMenus.spec.js.snap | 5 - .../NavBar/__snapshots__/index.spec.js.snap | 1459 +++++++--------- .../__snapshots__/DocsLayout.spec.js.snap | 1490 +++++++---------- .../{Link.spec.js.snap => Link.spec.tsx.snap} | 5 +- .../__snapshots__/NextPage.spec.js.snap | 1 - tsconfig.json | 2 +- utils/mdx-components.tsx | 102 ++ utils/sizes.js | 2 +- yarn.lock | 794 +++------ 43 files changed, 2404 insertions(+), 3518 deletions(-) rename companies-manifest.js => companies-manifest.tsx (99%) rename components/{Label.js => Label.tsx} (91%) rename components/{Layout.ts => Layout.tsx} (94%) delete mode 100644 components/Link.js create mode 100644 components/Link.tsx rename components/Nav/{Logo.js => Logo.tsx} (91%) rename components/Nav/{MobileNavbar.js => MobileNavbar.tsx} (67%) rename components/Nav/{NavButton.js => NavButton.tsx} (89%) rename components/Nav/{NavLinks.js => NavLinks.tsx} (97%) rename components/Nav/{Navbar.js => Navbar.tsx} (63%) delete mode 100644 components/Nav/Search.js delete mode 100644 components/Nav/SearchWithAlgolia.js create mode 100644 components/Nav/SearchWithAlgolia.tsx rename components/Nav/{SidebarMenus.js => SidebarMenus.tsx} (58%) rename components/Nav/{Social.js => Social.tsx} (84%) rename components/Nav/{index.js => index.tsx} (68%) rename components/UsersLogos/{index.js => index.tsx} (82%) rename components/{WithIsScrolled.js => WithIsScrolled.tsx} (79%) create mode 100644 global.d.ts delete mode 100644 pages/_app.js rename pages/{_document.js => _app.tsx} (78%) create mode 100644 pages/_document.tsx rename pages/{index.js => index.tsx} (55%) rename test/components/{Link.spec.js => Link.spec.tsx} (64%) delete mode 100644 test/components/NavBar/Search.spec.js delete mode 100644 test/components/NavBar/__snapshots__/Search.spec.js.snap rename test/components/__snapshots__/{Link.spec.js.snap => Link.spec.tsx.snap} (95%) create mode 100644 utils/mdx-components.tsx diff --git a/companies-manifest.js b/companies-manifest.tsx similarity index 99% rename from companies-manifest.js rename to companies-manifest.tsx index 339e61e84..f03ebff5f 100644 --- a/companies-manifest.js +++ b/companies-manifest.tsx @@ -1242,6 +1242,8 @@ const companies = [ }, ]; +export type ICompany = (typeof companies)[number]; + export default companies; // sorting logic: the more popular a website, the higher it gets listed diff --git a/components/Label.js b/components/Label.tsx similarity index 91% rename from components/Label.js rename to components/Label.tsx index ab34c5f66..5007d4e21 100644 --- a/components/Label.js +++ b/components/Label.tsx @@ -10,7 +10,7 @@ export const LabelGroup = styled.div` bottom: ${rem(3)}; `; -const Label = styled.small` +const Label = styled.small<{ $isVersion?: boolean }>` display: inline-block; background: ${p => (p.$isVersion ? 'royalblue' : blmGrey)}; color: white; diff --git a/components/Layout.ts b/components/Layout.tsx similarity index 94% rename from components/Layout.ts rename to components/Layout.tsx index 727ac2238..f78e982de 100644 --- a/components/Layout.ts +++ b/components/Layout.tsx @@ -13,7 +13,7 @@ export const Container = styled.div` `)}; `; -export const Content = styled.div<{ $hero: boolean; $moveRight: boolean }>` +export const Content = styled.div<{ $hero?: boolean; $moveRight?: boolean }>` max-width: 100%; margin: 0; padding: ${rem(90)} ${rem(60)} 0 ${rem(60)}; diff --git a/components/Link.js b/components/Link.js deleted file mode 100644 index 7b3be281f..000000000 --- a/components/Link.js +++ /dev/null @@ -1,58 +0,0 @@ -import styled from 'styled-components'; -import UnstyledLink from 'next/link'; - -import rem from '../utils/rem'; -import { red, blmGrey, lightGrey } from '../utils/colors'; - -export const StyledLink = styled.a` - display: inline-block; - color: inherit; - cursor: pointer; - padding: ${rem(2)} ${rem(8)}; - margin: ${rem(-2)} ${rem(-8)}; - - @media (min-width: ${1000 / 16}em) { - border-radius: ${rem(3)}; - - &:hover { - background: ${lightGrey}; - } - } -`; - -export const InlineLink = styled.a.attrs((/* props */) => ({ - target: '_blank', - rel: 'noopener', -}))` - color: ${p => (p['data-white'] ? 'white' : blmGrey)}; - cursor: pointer; - text-decoration: underline; - - &:hover { - color: ${p => (p['data-white'] ? 'white' : red)}; - } -`; - -const Link = ({ ['aria-label']: ariaLabel, children, className, inline, unstyled, white, ...rest }) => { - let Child = StyledLink; - if (inline) { - Child = InlineLink; - } else if (unstyled) { - Child = 'a'; - } - - let dataAttrs; - if (white) { - dataAttrs = { 'data-white': white }; - } - - return ( - - - {children} - - - ); -}; - -export default Link; diff --git a/components/Link.tsx b/components/Link.tsx new file mode 100644 index 000000000..02298dbbf --- /dev/null +++ b/components/Link.tsx @@ -0,0 +1,78 @@ +import UnstyledLink, { LinkProps as UnstyledLinkProps } from 'next/link'; +import React from 'react'; +import styled from 'styled-components'; +import { blmGrey, lightGrey, red } from '../utils/colors'; +import rem from '../utils/rem'; + +export const StyledLink = styled.a` + display: inline-block; + color: inherit; + cursor: pointer; + padding: ${rem(2)} ${rem(8)}; + margin: ${rem(-2)} ${rem(-8)}; + + @media (min-width: ${1000 / 16}em) { + border-radius: ${rem(3)}; + + &:hover { + background: ${lightGrey}; + } + } +`; + +export const InlineLink = styled.a.attrs((/* props */) => ({ + target: '_blank', + rel: 'noopener', +}))` + color: ${blmGrey}; + cursor: pointer; + text-decoration: underline; + + &:hover { + color: ${red}; + } + + &[data-white] { + color: white !important; + } +`; + +type AnchorProps = JSX.IntrinsicElements['a']; + +export interface LinkProps extends UnstyledLinkProps, Omit { + inline?: boolean; + ref?: React.Ref; + unstyled?: boolean; + white?: boolean; +} + +export default function Link({ + ['aria-label']: ariaLabel, + children, + className, + inline, + unstyled, + white, + ...rest +}: LinkProps) { + let Child: keyof JSX.IntrinsicElements | React.ComponentType = StyledLink; + + if (inline) { + Child = InlineLink; + } else if (unstyled) { + Child = 'a'; + } + + let dataAttrs; + if (white) { + dataAttrs = { 'data-white': white }; + } + + return ( + + + {children} + + + ); +} diff --git a/components/Nav/Logo.js b/components/Nav/Logo.tsx similarity index 91% rename from components/Nav/Logo.js rename to components/Nav/Logo.tsx index a100481b2..b821abe96 100644 --- a/components/Nav/Logo.js +++ b/components/Nav/Logo.tsx @@ -2,7 +2,7 @@ import styled, { css } from 'styled-components'; import rem from '../../utils/rem'; -const Logo = styled.div` +const Logo = styled.div<{ $compact?: boolean }>` display: inline-block; vertical-align: middle; box-sizing: border-box; diff --git a/components/Nav/MobileNavbar.js b/components/Nav/MobileNavbar.tsx similarity index 67% rename from components/Nav/MobileNavbar.js rename to components/Nav/MobileNavbar.tsx index 3bd01c100..409089ae5 100644 --- a/components/Nav/MobileNavbar.js +++ b/components/Nav/MobileNavbar.tsx @@ -1,29 +1,29 @@ -import React from 'react'; +import { KeyboardArrowDown } from '@styled-icons/material'; import styled, { css } from 'styled-components'; -import { Search, KeyboardArrowDown } from '@styled-icons/material'; -import rem from '../../utils/rem'; -import { navbarHeight } from '../../utils/sizes'; import { paleGrey } from '../../utils/colors'; import { mobile } from '../../utils/media'; -import { CloseIcon, FoldIcon } from './NavIcons'; +import rem from '../../utils/rem'; +import { navbarHeight } from '../../utils/sizes'; import Link from '../Link'; -import NavLinks from './NavLinks'; -import Social from './Social'; import Logo from './Logo'; import NavButton from './NavButton'; +import { CloseIcon, FoldIcon } from './NavIcons'; +import NavLinks from './NavLinks'; +import Social from './Social'; const Wrapper = styled.div` display: none; ${mobile(css` - display: flex; align-items: center; - justify-content: space-between; + display: flex; + flex-grow: 1; height: ${rem(navbarHeight)}; + justify-content: space-between; `)}; `; -const SecondaryMenu = styled.div` +const SecondaryMenu = styled.div<{ $isOpen?: boolean }>` position: absolute; top: ${rem(navbarHeight)}; left: 0; @@ -61,7 +61,7 @@ const LogoLink = styled(Link).attrs((/* props */) => ({ vertical-align: center; `; -const ArrowWrapper = styled.div` +const ArrowWrapper = styled.div<{ $shouldRotate?: boolean }>` transition: transform 0.2s; ${p => @@ -76,16 +76,29 @@ const SecondaryMenuItem = styled.div` padding-right: ${rem(20)}; `; -const StyledIcon = styled.div` +const StyledIcon = styled.div<{ $size?: number }>` && { - width: ${p => rem(p.size || 20)}; - height: ${p => rem(p.size || 20)}; + width: ${p => rem(p.$size || 20)}; + height: ${p => rem(p.$size || 20)}; } `; -const MobileNavbar = props => { - const { isSideFolded, isMobileNavFolded, onSideToggle, onMobileNavToggle, showSideNav, onSearchButtonClick } = props; - +export interface MobileNavbarProps { + isMobileNavFolded?: boolean; + isSideFolded?: boolean; + onMobileNavToggle?: () => void; + onSideToggle?: () => void; + showSideNav?: boolean; +} + +const MobileNavbar = ({ + children, + isMobileNavFolded, + isSideFolded, + onMobileNavToggle, + onSideToggle, + showSideNav, +}: React.PropsWithChildren) => { return ( {showSideNav !== false && ( @@ -97,17 +110,14 @@ const MobileNavbar = props => { -
- - - - - - - - -
+ {children} + + + + + + diff --git a/components/Nav/NavButton.js b/components/Nav/NavButton.tsx similarity index 89% rename from components/Nav/NavButton.js rename to components/Nav/NavButton.tsx index ca0fac3ba..ea03fcc08 100644 --- a/components/Nav/NavButton.js +++ b/components/Nav/NavButton.tsx @@ -1,10 +1,9 @@ import styled from 'styled-components'; - +import { resetInput } from '../../utils/form'; import rem from '../../utils/rem'; import { navbarHeight } from '../../utils/sizes'; -import { resetInput } from '../../utils/form'; -const NavButton = styled.button` +const NavButton = styled.button<{ $active?: boolean }>` ${resetInput}; background: ${p => (p.$active ? 'rgba(0, 0, 0, 0.07)' : 'none')}; cursor: pointer; diff --git a/components/Nav/NavLinks.js b/components/Nav/NavLinks.tsx similarity index 97% rename from components/Nav/NavLinks.js rename to components/Nav/NavLinks.tsx index 1433cadb4..2e0ddabe8 100644 --- a/components/Nav/NavLinks.js +++ b/components/Nav/NavLinks.tsx @@ -1,10 +1,8 @@ -import React from 'react'; import styled from 'styled-components'; - import rem from '../../utils/rem'; import { navbarHeight } from '../../utils/sizes'; -import NavSeparator from './NavSeparator'; import Link from '../Link'; +import NavSeparator from './NavSeparator'; const Wrapper = styled.nav` display: flex; diff --git a/components/Nav/Navbar.js b/components/Nav/Navbar.tsx similarity index 63% rename from components/Nav/Navbar.js rename to components/Nav/Navbar.tsx index 46b9780e9..3ef68e127 100644 --- a/components/Nav/Navbar.js +++ b/components/Nav/Navbar.tsx @@ -1,34 +1,37 @@ -import React, { PureComponent, createRef } from 'react'; -import styled, { css } from 'styled-components'; import { Close } from '@styled-icons/material'; -import rem from '../../utils/rem'; +import { PureComponent, createRef } from 'react'; +import styled, { css } from 'styled-components'; import { blmGrey, paleGrey } from '../../utils/colors'; -import { navbarHeight } from '../../utils/sizes'; import { headerFont } from '../../utils/fonts'; import { mobile } from '../../utils/media'; +import rem from '../../utils/rem'; +import { navbarHeight } from '../../utils/sizes'; import Link from '../Link'; -import NavLinks from './NavLinks'; -import Social from './Social'; import Logo from './Logo'; import MobileNavbar from './MobileNavbar'; +import NavLinks from './NavLinks'; import SearchWithAlgolia from './SearchWithAlgolia'; +import Social from './Social'; -const Wrapper = styled.nav` - position: fixed; - left: 0; +const Wrapper = styled.nav<{ $transparent?: boolean }>` + align-items: center; + background-color: rgba(12, 13, 15, 0.7); + backdrop-filter: blur(5px); box-sizing: border-box; - z-index: 3; - - width: 100%; - height: ${rem(navbarHeight)}; - + color: white; + display: flex; + flex-wrap: wrap; font-family: ${headerFont}; font-size: ${rem(15)}; font-weight: 500; - background: ${props => (props.$transparent ? 'transparent' : blmGrey)}; - transition: background 300ms ease-out; - color: white; + justify-content: center; + height: ${rem(navbarHeight)}; + left: 0; padding: 0; + position: fixed; + transition: background 300ms ease-out; + width: 100%; + z-index: 3; `; const StartWrapper = styled.div` @@ -51,6 +54,7 @@ const NormalNavbar = styled.div` align-items: center; padding: 0 ${rem(20)}; justify-content: space-between; + ${StartWrapper}, ${EndWrapper} ${StyledSocial} { ${mobile(css` display: none; @@ -77,11 +81,11 @@ const AlgoliaModal = styled.div` const baseZ = 1; -const AlgoliaModalHeader = styled.div` +const AlgoliaModalHeader = styled.div<{ $isOpen?: boolean }>` display: none; color: currentColor; - ${mobile(css` + ${mobile(css<{ $isOpen?: boolean }>` display: ${props => (props.$isOpen ? 'block' : 'none')}; button { @@ -96,10 +100,10 @@ const AlgoliaModalHeader = styled.div` `)}; `; -const AlgoliaModalOverlay = styled.div` +const AlgoliaModalOverlay = styled.div<{ $isOpen?: boolean }>` margin-right: ${rem(10)}; - ${mobile(css` + ${mobile(css<{ $isOpen?: boolean }>` position: fixed; top: 0; bottom: 0; @@ -121,17 +125,20 @@ const AlgoliaModalOverlay = styled.div` `)}; `; -class ModalContainer extends PureComponent { - modalElement = createRef(); - onModalOverlayClick = e => { - if (!this.modalElement.current.contains(e.target)) { +class ModalContainer extends PureComponent<{ isOpen?: boolean; requestModalClose: () => void }> { + modalElement = createRef(); + + onModalOverlayClick = (e: React.MouseEvent) => { + if (!this.modalElement.current?.contains(e.target as Node)) { this.props.requestModalClose(); } }; - onCloseButtonClick = e => { + + onCloseButtonClick = (e: React.MouseEvent) => { e.stopPropagation(); this.props.requestModalClose(); }; + render() { return ( <> @@ -159,42 +166,49 @@ const LogoLink = styled(Link).attrs((/* props */) => ({ margin-right: ${rem(35)}; `; -class Navbar extends PureComponent { - state = { - isOpen: false, - }; - openModal = () => this.setState(() => ({ isOpen: true })); - closeModal = () => this.setState(({ isOpen }) => (isOpen ? { isOpen: false } : null)); - render() { - const { onSideToggle, onMobileNavToggle, isSideFolded, isMobileNavFolded, showSideNav, transparent } = this.props; +export interface NavbarProps { + onSideToggle?: () => void; + onMobileNavToggle?: () => void; + isSideFolded?: boolean; + isMobileNavFolded?: boolean; + showSideNav?: boolean; +} - return ( - - - - - - - - - - - - - - - - - - ); - } +function Navbar({ onSideToggle, onMobileNavToggle, isSideFolded, isMobileNavFolded, showSideNav }: NavbarProps) { + return ( + + + + + + + + + + + + + + + + + + ); } export default Navbar; diff --git a/components/Nav/Search.js b/components/Nav/Search.js deleted file mode 100644 index 25d5cccfa..000000000 --- a/components/Nav/Search.js +++ /dev/null @@ -1,179 +0,0 @@ -import { Search as SearchIcon } from '@styled-icons/material'; -import PropTypes from 'prop-types'; -import React, { useEffect, useRef } from 'react'; -import styled, { createGlobalStyle, css } from 'styled-components'; -import { blmGrey, blmLightGrey, darkGrey, grey } from '../../utils/colors'; -import { resetInput } from '../../utils/form'; -import { mobile } from '../../utils/media'; -import rem from '../../utils/rem'; -import { navbarHeight } from '../../utils/sizes'; - -const StyledSearchIcon = styled(SearchIcon)``; - -const INPUT_ID = 'docs-search-input'; - -const Wrapper = styled.form` - display: flex; - align-items: center; - justify-content: flex-start; - flex: 0 0 auto; - ${mobile(css` - display: block; - padding: 0; - span.algolia-autocomplete { - display: block !important; - } - span.ds-dropdown-menu::before { - display: none; - } - `)}; -`; - -const Input = styled.input` - ${resetInput}; - appearance: none; - flex: 0 0 auto; - width: ${rem(130)}; - line-height: ${rem(navbarHeight - 20)}; - font-size: ${rem(15)}; - color: currentColor; - - ::placeholder { - color: currentColor; - opacity: 0.5; - transition: opacity 0.2s ease-in-out; - } - - :focus { - ::placeholder { - opacity: 0.8; - } - } - - ${mobile(css` - padding: ${rem(10)} ${rem(48)}; - display: block; - width: 100%; - background: ${blmGrey}; - color: white; - `)}; -`; - -const Button = styled.label.attrs((/* props */) => ({ - htmlFor: INPUT_ID, -}))` - ${resetInput}; - flex: 0 0 auto; - ${resetInput} flex: 0 0 auto; - margin-right: ${rem(4)}; - cursor: pointer; - display: flex; - &:hover, - &:focus { - opacity: 0.7; - filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.2)); - } - - &:active { - background: rgba(0, 0, 0, 0.1); - } - - ${StyledSearchIcon} { - width: ${rem(26)}; - height: ${rem(26)}; - } - - ${mobile(css` - position: absolute; - top: ${rem(9)}; - left: ${rem(10)}; - height: ${rem(32)}; - display: flex; - align-items: center; - justify-content: center; - width: ${rem(32)}; - z-index: 1; - background: none; - color: white; - `)}; -`; - -const GlobalStyles = createGlobalStyle` - .algolia-autocomplete - .ds-dropdown-menu - .ds-suggestion - .algolia-docsearch-suggestion--content { - width: 100% !important; - - &::before { - content: none; - } - } - - .algolia-autocomplete - .ds-dropdown-menu - .ds-suggestion.ds-cursor - .algolia-docsearch-suggestion--content { - background: ${blmLightGrey} !important; - } - - /* Main category (eg. Getting Started) */ - .algolia-autocomplete .algolia-docsearch-suggestion--category-header { - color: ${darkGrey}; - } - - /* Category (eg. Downloads) */ - .algolia-autocomplete .algolia-docsearch-suggestion--subcategory-column { - display: none !important; - } - - /* Title (eg. Bootstrap CDN) */ - .algolia-autocomplete .algolia-docsearch-suggestion--title { - font-weight: bold; - color: black; - } - - /* Description description (eg. Bootstrap currently works...) */ - .algolia-autocomplete .algolia-docsearch-suggestion--text { - color: ${grey}; - } - - /* Highlighted text */ - .algolia-autocomplete .algolia-docsearch-suggestion--highlight { - box-shadow: none !important; - color: ${blmGrey} !important; - background: transparent !important; - } - - .algolia-autocomplete .ds-dropdown-menu { - margin-top: 0; - - > *:first-child { - border: 0; - } - } -`; - -const Search = ({ isDocs, className }) => { - const searchInput = useRef(null); - - useEffect(() => { - if (searchInput.current) searchInput.current.focus(); - }, []); - - return ( - - - - - - ); -}; - -Search.propTypes = { - className: PropTypes.string, -}; - -export default Search; diff --git a/components/Nav/SearchWithAlgolia.js b/components/Nav/SearchWithAlgolia.js deleted file mode 100644 index 75606a562..000000000 --- a/components/Nav/SearchWithAlgolia.js +++ /dev/null @@ -1,30 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import Router from 'next/router'; -import Search from './Search'; - -export default function AlgoliaSearch({ className, requestModalClose }) { - const [isDocs, setIsDocs] = useState(false); - - useEffect(() => { - if (process.browser && Router.pathname.startsWith('/docs')) setIsDocs(true); - - if (process.env.NODE_ENV !== 'test') { - import('docsearch.js').then(mdl => { - mdl.default({ - apiKey: '79886fb59ad3ebe2002b481cffbbe7cb', - indexName: 'styled-components', - inputSelector: '#docs-search-input', - debug: true, // Set debug to true if you want to inspect the dropdown - handleSelected: (input, event, suggestion) => { - // original handleselect - requestModalClose(); - input.setVal(''); - window.location.assign(suggestion.url); - }, - }); - }); - } - }, [requestModalClose]); - - return ; -} diff --git a/components/Nav/SearchWithAlgolia.tsx b/components/Nav/SearchWithAlgolia.tsx new file mode 100644 index 000000000..4360d76d7 --- /dev/null +++ b/components/Nav/SearchWithAlgolia.tsx @@ -0,0 +1,25 @@ +import Router from 'next/router'; +import { useEffect, useState } from 'react'; +const INPUT_ID = 'docs-search-input'; + +export default function AlgoliaSearch(props: JSX.IntrinsicElements['div']) { + const [isDocs, setIsDocs] = useState(false); + + useEffect(() => { + if (process.browser && Router.pathname.startsWith('/docs')) setIsDocs(true); + + if (process.env.NODE_ENV !== 'test') { + import('@docsearch/js').then(mdl => { + mdl.default({ + apiKey: '79886fb59ad3ebe2002b481cffbbe7cb', + appId: 'BH4D9OD16A', + container: '#docs-search-input', + indexName: 'styled-components', + placeholder: isDocs ? `Search ...` : `Search docs ...`, + }); + }); + } + }, []); + + return
; +} diff --git a/components/Nav/SidebarMenus.js b/components/Nav/SidebarMenus.tsx similarity index 58% rename from components/Nav/SidebarMenus.js rename to components/Nav/SidebarMenus.tsx index 725c271a7..0507959c8 100644 --- a/components/Nav/SidebarMenus.js +++ b/components/Nav/SidebarMenus.tsx @@ -1,10 +1,9 @@ -import React, { useCallback, useState, useEffect } from 'react'; -import styled from 'styled-components'; import { useRouter, withRouter } from 'next/router'; - +import React, { useCallback, useEffect, useState } from 'react'; +import styled from 'styled-components'; +import json from '../../pages/docs.json'; import rem from '../../utils/rem'; import titleToDash from '../../utils/titleToDash'; -import json from '../../pages/docs.json'; import Link, { StyledLink } from '../Link'; const { pages } = json; @@ -38,7 +37,12 @@ const SubSection = styled.h5` font-weight: normal; `; -function Folder({ children, isOpenDefault = false, ...props }) { +export interface FolderProps { + children?: (options: { rootProps: FolderProps; toggleSubSections: () => void; isOpen?: boolean }) => JSX.Element; + isOpenDefault?: boolean; +} + +function Folder({ children, isOpenDefault = false, ...props }: FolderProps) { const [isOpen, setIsOpen] = useState(isOpenDefault); const toggleSubSections = useCallback(() => { @@ -60,41 +64,57 @@ function Folder({ children, isOpenDefault = false, ...props }) { : null; } -export const DocsSidebarMenu = withRouter(({ onRouteChange, router }) => ( - - {pages.map(({ title, pathname, sections }) => ( - - {({ rootProps, toggleSubSections, isOpen }) => ( -
- - {title} - - - {isOpen && - sections.map(({ title }) => ( - - {title} - - ))} -
- )} -
- ))} -
-)); - -function getSectionPath(parentPathname, title) { - return `${parentPathname || ''}#${titleToDash(title)}`; +export interface DocsSidebarMenuProps { + onRouteChange?: () => void; } -function isFolderOpen(currentHref, { pathname, title, sections }) { +export const DocsSidebarMenu = ({ onRouteChange }: DocsSidebarMenuProps) => { + const router = useRouter(); + return ( - sections.reduce((sum, v) => sum || currentHref.endsWith(getSectionPath(pathname, v.title)), false) || + + {pages.map(({ title, pathname, sections }) => ( + + {({ rootProps, toggleSubSections, isOpen }) => ( +
+ + {title} + + + {isOpen && + sections.map(({ title }) => ( + + {title} + + ))} +
+ )} +
+ ))} +
+ ); +}; + +function getSectionPath(parentPathname: string, title: string) { + return `${parentPathname}#${titleToDash(title)}`; +} + +function isFolderOpen( + currentHref: string, + { pathname, title, sections }: { pathname: string; title: string; sections: { title: string }[] } +) { + return ( + sections.reduce((sum, v) => sum || currentHref.endsWith(getSectionPath(pathname, v.title) || ''), false) || currentHref.endsWith(pathname || '#' + titleToDash(title)) ); } -export const SimpleSidebarMenu = ({ onRouteChange, pages = [] }) => { +export interface SimpleSidebarMenuProps { + onRouteChange?: () => void; + pages?: { title: string; pathname: string; sections: { title: string }[]; href: string }[]; +} + +export const SimpleSidebarMenu = ({ onRouteChange, pages = [] }: SimpleSidebarMenuProps) => { const router = useRouter(); return ( diff --git a/components/Nav/Social.js b/components/Nav/Social.tsx similarity index 84% rename from components/Nav/Social.js rename to components/Nav/Social.tsx index c414c2e1d..2e6eede39 100644 --- a/components/Nav/Social.js +++ b/components/Nav/Social.tsx @@ -41,10 +41,10 @@ const SocialLink = styled(Link).attrs((/* props */) => ({ } `; -const StyledIcon = styled.div` +const StyledIcon = styled.div<{ $height?: number; $width?: number }>` && { - width: ${p => rem(Number(p.width))}; - height: ${p => rem(Number(p.height))}; + width: ${p => rem(Number(p.$width))}; + height: ${p => rem(Number(p.$height))}; } `; @@ -58,16 +58,16 @@ const StyledIcon = styled.div` // // ) -const Social = props => ( +const Social = (props: React.ComponentProps) => ( {/* */} - + - + ); diff --git a/components/Nav/index.js b/components/Nav/index.tsx similarity index 68% rename from components/Nav/index.js rename to components/Nav/index.tsx index f12eb4b46..1df71314b 100644 --- a/components/Nav/index.js +++ b/components/Nav/index.tsx @@ -1,9 +1,20 @@ import React from 'react'; import Navbar from './Navbar'; import Sidebar from './Sidebar'; -import { DocsSidebarMenu, SimpleSidebarMenu } from './SidebarMenus'; +import { DocsSidebarMenu, SimpleSidebarMenu, SimpleSidebarMenuProps } from './SidebarMenus'; -const Nav = props => { +interface NavProps { + isSideFolded?: boolean; + isMobileNavFolded?: boolean; + onSideToggle?: () => void; + onMobileNavToggle?: () => void; + onRouteChange?: () => void; + showSideNav?: boolean; + useDocsSidebarMenu?: boolean; + pages?: SimpleSidebarMenuProps['pages']; +} + +const Nav = (props: NavProps) => { const { isSideFolded, isMobileNavFolded, @@ -12,7 +23,6 @@ const Nav = props => { onRouteChange, showSideNav, useDocsSidebarMenu, - transparent, pages, } = props; @@ -20,7 +30,6 @@ const Nav = props => {
keyframes` +const getSlide = (childIndex: number, reverse?: boolean) => keyframes` from { transform: translateX(${childIndex * 100}%); } @@ -26,7 +27,7 @@ const UsersSliderContainer = styled.div` position: relative; `; -const UsersSlider = styled.span` +const UsersSlider = styled.span<{ $offset?: number; $reverse?: boolean }>` display: inline-block; animation: ${({ $offset, $reverse }) => getSlide($offset || 0, $reverse)} 150s linear infinite; white-space: nowrap; @@ -61,7 +62,11 @@ const CompanyLogo = styled.span` } `; -const SortedLogos = ({ users }) => ( +export interface ISortedLogos { + users: ICompany[]; +} + +const SortedLogos = ({ users }: ISortedLogos) => ( {/* TODO: remove this check after adding missing logos */} {users.map( @@ -75,7 +80,7 @@ const SortedLogos = ({ users }) => ( ); -const UsersLogos = ({ users, reverse }) => { +const UsersLogos = ({ users, reverse }: { reverse?: boolean; users: ISortedLogos['users'] }) => { return ( diff --git a/components/WithIsScrolled.js b/components/WithIsScrolled.tsx similarity index 79% rename from components/WithIsScrolled.js rename to components/WithIsScrolled.tsx index 2d377b0f2..8001192e2 100644 --- a/components/WithIsScrolled.js +++ b/components/WithIsScrolled.tsx @@ -1,7 +1,6 @@ -import invariant from 'invariant'; import { useCallback, useEffect, useState } from 'react'; -export default function WithIsScrolled(props) { +export default function WithIsScrolled(props: { children: React.FC<{ isScrolled: boolean }> }) { const [isScrolled, setIsScrolled] = useState(false); const onScroll = useCallback(() => { @@ -9,11 +8,10 @@ export default function WithIsScrolled(props) { }, []); useEffect(() => { - invariant(typeof props.children === 'function', 'The children prop is expected to be a function'); - // Learn more about how { passive: true } improves scrolling performance // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners window.addEventListener('scroll', onScroll, { passive: true }); + return () => window.removeEventListener('scroll', onScroll, { passive: true }); }, [onScroll, props.children]); diff --git a/global.d.ts b/global.d.ts new file mode 100644 index 000000000..8b37298ef --- /dev/null +++ b/global.d.ts @@ -0,0 +1,11 @@ +import { CSSProp, StyledObject } from 'styled-components'; + +interface EventListenerOptions { + passive?: boolean; +} + +declare module 'react' { + interface DOMAttributes { + css?: string | StyledObject; + } +} diff --git a/next.config.mjs b/next.config.mjs index 359c6f2e5..d2f4c1471 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -14,7 +14,7 @@ export default withMDX({ compiler: { styledComponents: true, }, - pageExtensions: ['js', 'jsx', 'md', 'mdx'], + pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'], poweredByHeader: false, webpack: function (config, { dev, isServer }) { if (dev) { diff --git a/package.json b/package.json index d0aa2bcf4..f2ec9f655 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "postinstall": "husky install" }, "dependencies": { + "@docsearch/js": "^3.0.0", "@mdx-js/loader": "^2.3.0", "@mdx-js/mdx": "^2.3.0", "@mdx-js/react": "^2.3.0", @@ -26,7 +27,6 @@ "@zeit/next-mdx": "^1.2.0", "@zeit/next-source-maps": "^0.0.3", "axios": "^0.24.0", - "docsearch.js": "2.6.3", "invariant": "^2.2.4", "isomorphic-fetch": "^3.0.0", "markdown-to-jsx": "^7.2.0", @@ -48,9 +48,11 @@ "@types/mdx": "^2.0.5", "@types/node": "^18.16.14", "@types/react": "^17.0.59", + "@types/react-test-renderer": "^17.0.0", "@types/webpack-bundle-analyzer": "^4.6.0", "@types/zeit__next-source-maps": "^0.0.2", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.7", + "csstype": "^3.1.2", "enzyme": "^3.11.0", "enzyme-to-json": "^3.6.2", "husky": "^8.0.3", @@ -59,7 +61,7 @@ "jest-styled-components": "^7.1.1", "lint-staged": "^13.2.2", "prettier": "^2.8.8", - "react-test-renderer": "^17.0.2", + "react-test-renderer": "^17.0.0", "remark-gfm": "^3.0.1", "rimraf": "^3.0.2", "ts-jest": "^29.1.0", diff --git a/pages/_app.js b/pages/_app.js deleted file mode 100644 index 5c177c0c7..000000000 --- a/pages/_app.js +++ /dev/null @@ -1,28 +0,0 @@ -import { MDXProvider } from '@mdx-js/react'; -import components from '../utils/mdx-components'; -import App from 'next/app'; -import Head from 'next/head'; -import React from 'react'; - -export default class MyApp extends App { - render() { - const { Component, pageProps } = this.props; - - return ( - <> - - - - - - - - - - - - - - ); - } -} diff --git a/pages/_document.js b/pages/_app.tsx similarity index 78% rename from pages/_document.js rename to pages/_app.tsx index 44190aa3e..016460961 100644 --- a/pages/_document.js +++ b/pages/_app.tsx @@ -1,16 +1,43 @@ -import Document, { Head, Html, Main, NextScript } from 'next/document'; -import { ServerStyleSheet } from 'styled-components'; - +import { MDXProvider } from '@mdx-js/react'; +import App from 'next/app'; +import Head from 'next/head'; +import { createGlobalStyle } from 'styled-components'; import { bodyFont } from '../utils/fonts'; -import { reset } from '../utils/scope'; +import components from '../utils/mdx-components'; + +export default class MyApp extends App { + render() { + const { Component, pageProps } = this.props; + + return ( + <> + + + + + + + + + -const resetStyles = ` + + + + + + + ); + } +} + +const ResetStyles = createGlobalStyle` *,::after,::before{background-repeat:no-repeat;box-sizing:inherit}::after,::before{text-decoration:inherit;vertical-align:inherit}html{box-sizing:border-box;cursor:default;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}article,aside,footer,header,nav,section{display:block}body{margin:0}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}nav ol,nav ul{list-style:none}pre{font-family:monospace,monospace;font-size:1em}a{text-decoration:none;color:inherit;background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,canvas,iframe,img,svg,video{vertical-align:middle}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}table{border-collapse:collapse}button,input,optgroup,select,textarea{margin:0}button,input,select,textarea{background-color:transparent;color:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button; cursor: pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto;resize:vertical}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[tabindex],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation;touch-action:manipulation}[hidden]{display:none}[aria-busy=true]{cursor:progress}[aria-controls]{cursor:pointer}[aria-hidden=false][hidden]:not(:focus){clip:rect(0,0,0,0);display:inherit;position:absolute}[aria-disabled]{cursor:default} - + #__next { overflow-x: hidden; } - + html, body { font-size: 18px; line-height: 1.6; @@ -70,40 +97,3 @@ const resetStyles = ` } } `; - -export default class MyDocument extends Document { - static getInitialProps({ renderPage }) { - const sheet = new ServerStyleSheet(); - - const page = renderPage(Component => props => sheet.collectStyles()); - - const styleElements = sheet.getStyleElement(); - return { ...page, styleElements }; - } - - render() { - const { styleElements } = this.props; - // reset counter for SSR - reset(); - - return ( - - - - - -