Skip to content

Commit

Permalink
Set up Next.js docs skeleton site (#870)
Browse files Browse the repository at this point in the history
  • Loading branch information
danoc authored Jan 9, 2023
1 parent a04e811 commit 489aa92
Show file tree
Hide file tree
Showing 18 changed files with 1,386 additions and 4 deletions.
2 changes: 2 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.2.1.cjs

enableGlobalCache: true

preferInteractive: true
36 changes: 36 additions & 0 deletions next/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
36 changes: 36 additions & 0 deletions next/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.

[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.

The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
64 changes: 64 additions & 0 deletions next/components/doc-search/doc-search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react';
import mousetrap from 'mousetrap';

import 'docsearch.js/dist/npm/styles/main.scss';

interface DocSearchProps {
children: (props: { id: string }) => JSX.Element;
}

export default class DocSearch extends React.Component<DocSearchProps> {
inputSelector: string;

constructor(props: DocSearchProps) {
super(props);
this.inputSelector = 'thumbprint-algolia-doc-search';

this.focusInput = this.focusInput.bind(this);
}

async componentDidMount(): Promise<void> {
// Focus on search when `/` is pressed.
if (typeof window !== 'undefined') {
mousetrap.bind(['/'], this.focusInput, 'keyup');
}

// eslint-disable-next-line global-require, @typescript-eslint/no-var-requires
const docsearch = require('docsearch.js');

docsearch({
apiKey: 'e5314d1bc146a7d26433a00e2031794c',
indexName: 'thumbprint',
inputSelector: `#${this.inputSelector}`,
transformData(suggestions: { url: string }[]) {
if (process.env.NODE_ENV === 'production') {
return suggestions;
}

return suggestions.map(suggestion => ({
...suggestion,
url: suggestion.url.replace(
'https://thumbprint.design/',
'http://localhost:8090/',
),
}));
},
});
}

componentWillUnmount(): void {
if (typeof window !== 'undefined') {
mousetrap.unbind(['/'], this.focusInput);
}
}

focusInput(): void {
document.getElementById(this.inputSelector)?.focus();
}

render(): JSX.Element {
const { children } = this.props;

return children({ id: this.inputSelector });
}
}
8 changes: 8 additions & 0 deletions next/components/layout/layout.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.container {
display: flex;
grid-template-columns: 256px 1fr;
}

.sidenav {
box-shadow: 1px 0px 4px rgba(0, 0, 0, 0.1);
}
164 changes: 164 additions & 0 deletions next/components/layout/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import OutsideClickHandler from 'react-outside-click-handler';
import { ScrollMarkerContainer } from 'react-scroll-marker';
import React, { createContext, useRef, useState } from 'react';
import { TextInput, TextInputIcon } from '@thumbtack/thumbprint-react';
import ClickableBox from 'clickable-box';
import classNames from 'classnames';
import {
NavigationSearchSmall,
NavigationCloseSmall,
NavigationHamburgerMedium,
} from '@thumbtack/thumbprint-icons';
import Link from 'next/link';
import Image from 'next/image';
import logo from '../../images/thumbprint-logo.svg';
import styles from './layout.module.scss';
import type { LayoutProps } from '../../utils/get-layout-props';
import SideNav, { SideNavGroup, SideNavLink } from '../side-nav/side-nav';
import DocSearch from '../doc-search/doc-search';

export const ActiveSectionContext = createContext<string | null | undefined>(null);

export default function Layout({
activeSection,
navigation,
children,
}: LayoutProps & {
children: React.ReactNode;
}): JSX.Element {
const [searchValue, setSearchValue] = useState<string | undefined>(undefined);
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const sidebarCloseEl = useRef();
const sidebarOpenEl = useRef();

return (
<div className="flex h-100">
<ActiveSectionContext.Provider value={activeSection}>
<ScrollMarkerContainer>
<OutsideClickHandler
onOutsideClick={(): void => {
if (isSidebarOpen) {
setIsSidebarOpen(false);
}
}}
>
<div
className={classNames(styles.sidenav, {
'bg-white w6 flex-column flex-none fixed h-100 z-1': true,
'dn l_flex': !isSidebarOpen,
flex: isSidebarOpen,
})}
>
<div className="ph3 pv4 flex-none z-1 bb b-gray-300 bg-gray-200">
<Link href="/" className="db mb3">
<Image
src={logo}
className="db"
style={{ width: '130px', height: '22px' }}
alt="Thumbprint logo"
/>
</Link>

<DocSearch>
{({ id }): JSX.Element => (
<TextInput
type="search"
size="small"
placeholder="Search"
onChange={(v): void => {
setSearchValue(v);
}}
value={searchValue}
id={id}
innerLeft={
<TextInputIcon>
<NavigationSearchSmall />
</TextInputIcon>
}
/>
)}
</DocSearch>
</div>
<ClickableBox
className="pa2 absolute top0 right0 pointer l_dn z-1"
onClick={(): void => {
setIsSidebarOpen(false);
}}
aria-label="Close sidebar navigation"
ref={sidebarCloseEl}
>
<NavigationCloseSmall className="db" />
</ClickableBox>

<SideNav>
{navigation.map(section => (
<SideNavLink
title={section.title}
isActive={activeSection === section.title}
href={section.href}
key={section.href}
level={1}
>
{section.groups.map((sectionGroup, i) => (
// There's no unique identifier for the group, so we use the index.
// eslint-disable-next-line react/no-array-index-key
<SideNavGroup key={`${section.title}-${i}`} level={2}>
{sectionGroup.map(groupItem =>
groupItem.sections ? (
<SideNavLink
title={groupItem.title}
level={2}
href={groupItem.href}
isActive={false}
key={groupItem.href}
>
<SideNavGroup level={3}>
{groupItem.sections?.map(
sectionItem => (
<SideNavLink
title={
sectionItem.title
}
href={sectionItem.href}
key={sectionItem.href}
level={3}
/>
),
)}
</SideNavGroup>
</SideNavLink>
) : (
<SideNavLink
title={groupItem.title}
key={groupItem.href}
level={2}
href={groupItem.href}
isActive={false}
/>
),
)}
</SideNavGroup>
))}
</SideNavLink>
))}
</SideNav>
</div>
</OutsideClickHandler>
<div className="flex-1 l_ml8">
<ClickableBox
className="inline-flex pv3 ph4 pointer l_dn"
onClick={(): void => {
setIsSidebarOpen(true);
}}
aria-label="Open sidebar navigation"
ref={sidebarOpenEl}
>
<NavigationHamburgerMedium />
</ClickableBox>
{children}
</div>
</ScrollMarkerContainer>
</ActiveSectionContext.Provider>
</div>
);
}
62 changes: 62 additions & 0 deletions next/components/side-nav/side-nav.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
@import '~@thumbtack/thumbprint-tokens/dist/scss/_index';

.sideNav {
// Momentum scrolling on iOS.
-webkit-overflow-scrolling: touch;
}

.sideNavGroup:not(:last-of-type) {
position: relative;

&:after {
content: '';
position: absolute;
height: 1px;
background: $tp-color__gray-300;
}
}

.sideNavGroupLevel2:not(:last-of-type) {
margin-bottom: $tp-space__3;

&:after {
left: $tp-space__4;
right: $tp-space__4;
bottom: -($tp-space__2);
}
}

.sideNavGroupLevel3:not(:last-of-type) {
margin-bottom: $tp-space__2;

&:after {
left: $tp-space__5;
right: $tp-space__5;
bottom: -($tp-space__1);
}
}

.sideNavClickableBox {
user-select: none;
cursor: pointer;
}

.sideNavLinkBlueActiveIndicator::before {
content: '';
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: $tp-space__1;
background: $tp-color__blue;
}

.sideNavBottomFooterLinks:not(:last-child) {
margin-right: $tp-space__2;
&:after {
// Adds a dot in between the links.
color: $tp-color__black-300;
content: '\2022';
padding-left: $tp-space__2;
}
}
Loading

0 comments on commit 489aa92

Please sign in to comment.