├── .env.local.example ├── postcss.config.js ├── public ├── favicon.ico └── vercel.svg ├── components ├── shared │ ├── tab-selector │ │ ├── Selector │ │ │ ├── Selector.module.css │ │ │ └── index.js │ │ ├── Item.module.css │ │ └── Item.js │ ├── Head │ │ └── index.js │ ├── Close │ │ ├── index.js │ │ └── Close.module.css │ └── form │ │ └── InputSearch.js ├── docs │ ├── BodyContent │ │ ├── BodyContent.module.css │ │ └── index.js │ ├── SearchResults │ │ ├── SearchResults.module.css │ │ ├── SearchItem │ │ │ ├── SearchItem.module.css │ │ │ └── index.js │ │ └── index.js │ ├── Sidebar.js │ ├── Main │ │ ├── index.js │ │ └── Main.module.css │ ├── Accordion.js │ └── IndexPage │ │ └── index.js └── icons │ └── form │ └── InputIcons.js ├── jsconfig.json ├── pages ├── _app.js ├── api │ └── content │ │ └── docs.js └── docs │ ├── index.js │ └── [slug].js ├── styles └── globals.css ├── next.config.js ├── .gitignore ├── package.json ├── containers └── docs │ └── index.js ├── LICENSE ├── README.md ├── lib └── content │ └── docs.js └── tailwind.config.js /.env.local.example: -------------------------------------------------------------------------------- 1 | README_AUTH_TOKEN= -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {plugins: ['tailwindcss', 'postcss-preset-env']} -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scaleapi/custom-documentation-example/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /components/shared/tab-selector/Selector/Selector.module.css: -------------------------------------------------------------------------------- 1 | .withFilter { 2 | backdrop-filter: blur(20px); 3 | } 4 | -------------------------------------------------------------------------------- /components/shared/tab-selector/Item.module.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 992px) { 2 | .smallText { 3 | font-size: 15px; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "docs/*": ["./*"] 6 | }, 7 | }, 8 | 9 | } -------------------------------------------------------------------------------- /pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | 3 | function MyApp({ Component, pageProps }) { 4 | return 5 | } 6 | 7 | export default MyApp 8 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --font-size: 1.6rem; 7 | --line-height: 0.8rem; 8 | font-size: 62.5%; 9 | } -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | async redirects() { 3 | return [ 4 | { 5 | source: '/', 6 | destination: '/docs', 7 | permanent: true, 8 | }, 9 | ] 10 | }, 11 | } -------------------------------------------------------------------------------- /pages/api/content/docs.js: -------------------------------------------------------------------------------- 1 | import { getDocsSearch } from 'docs/lib/content/docs'; 2 | 3 | export default async function DocsEntry(req, res) { 4 | const results = await getDocsSearch(req.query.search); 5 | res.json(results); 6 | } 7 | -------------------------------------------------------------------------------- /components/docs/BodyContent/BodyContent.module.css: -------------------------------------------------------------------------------- 1 | @media (min-width: 992px) { 2 | .bodyContent { 3 | box-shadow: -2px 2px 2px rgba(74, 74, 139, 0.01), -3px 4px 3px rgba(74, 74, 139, 0.03); 4 | } 5 | } 6 | 7 | .inputWrapper { 8 | width: 50%; 9 | max-width: 288px; 10 | flex-shrink: 1; 11 | } 12 | -------------------------------------------------------------------------------- /components/shared/Head/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Head from 'next/head'; 3 | 4 | const SiteHead = ({ description, title }) => ( 5 | 6 | 7 | 8 | {title} 9 | 10 | ); 11 | 12 | SiteHead.defaultProps = { 13 | title: 'Docs', 14 | description: 'Docs', 15 | }; 16 | 17 | export default SiteHead; 18 | -------------------------------------------------------------------------------- /components/docs/SearchResults/SearchResults.module.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | max-height: 478px; 3 | min-height: 300px; 4 | position: absolute; 5 | top: 75px; 6 | left: 64px; 7 | right: 0; 8 | animation: fadeIn 250ms ease-out forwards; 9 | opacity: 0; 10 | transform: translate3d(0, -8px, 0); 11 | } 12 | 13 | @keyframes fadeIn { 14 | 0% { 15 | opacity: 0; 16 | transform: translate3d(0, -16px, 0); 17 | } 18 | 100% { 19 | opacity: 1; 20 | transform: translate3d(0, 0, 0); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /components/docs/SearchResults/SearchItem/SearchItem.module.css: -------------------------------------------------------------------------------- 1 | .wrapper em { 2 | background: #fbe290; 3 | font-style: normal; 4 | } 5 | 6 | @media (prefers-color-scheme: dark) { 7 | .wrapper em { 8 | background: #807855; 9 | } 10 | } 11 | 12 | .link { 13 | padding-left: 34px; 14 | padding-right: 34px; 15 | } 16 | 17 | .badge { 18 | font-size: 1.2rem; 19 | height: 24px; 20 | line-height: 24px; 21 | } 22 | 23 | .post { 24 | background: #ffca28; 25 | } 26 | 27 | .get { 28 | background: #72ce7b; 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # env 4 | .env 5 | .env.local 6 | 7 | # dependencies 8 | /node_modules 9 | /.pnp 10 | .pnp.js 11 | 12 | # testing 13 | /coverage 14 | 15 | # next.js 16 | /.next/ 17 | /out/ 18 | 19 | # production 20 | /build 21 | 22 | # misc 23 | .DS_Store 24 | *.pem 25 | 26 | # debug 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | 31 | # local env files 32 | .env.local 33 | .env.development.local 34 | .env.test.local 35 | .env.production.local 36 | 37 | # vercel 38 | .vercel 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs-blogpost", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "classnames": "^2.2.6", 12 | "dayjs": "^1.9.1", 13 | "next": "9.5.3", 14 | "nprogress": "^0.2.0", 15 | "postcss-preset-env": "^6.7.0", 16 | "react": "16.13.1", 17 | "react-aria-tabpanel": "^4.4.0", 18 | "react-dom": "16.13.1", 19 | "react-sticky-box": "^0.9.3", 20 | "react-use": "^15.3.4", 21 | "tailwindcss": "^1.8.10" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /containers/docs/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import SiteHead from 'docs/components/shared/Head'; 4 | import Main from 'docs/components/docs/Main'; 5 | 6 | const Docs = ({ 7 | topLevelNavigation, 8 | subLevelNavigation, 9 | title, 10 | content, 11 | slug, 12 | updated 13 | }) => ( 14 | <> 15 | 16 |
24 | 25 | ); 26 | 27 | export default Docs; 28 | -------------------------------------------------------------------------------- /pages/docs/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { getDocsCategories, getDocsSubcategories } from 'docs/lib/content/docs'; 4 | 5 | import DocsContainer from 'docs/containers/docs'; 6 | 7 | export async function getStaticProps() { 8 | const props = {}; 9 | try { 10 | props.topLevelNavigation = await getDocsCategories(); 11 | props.subLevelNavigation = await getDocsSubcategories(); 12 | } catch (error) { 13 | throw error; 14 | } 15 | return { props, revalidate: 1 }; 16 | } 17 | 18 | const Docs = ({ topLevelNavigation, subLevelNavigation }) => { 19 | return ( 20 | 24 | ); 25 | }; 26 | 27 | export default Docs; 28 | -------------------------------------------------------------------------------- /components/shared/Close/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cn from 'classnames'; 3 | 4 | import styles from './Close.module.css'; 5 | 6 | export default class Close extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { isToggleOn: true }; 10 | this.handleClick = this.handleClick.bind(this); 11 | } 12 | 13 | handleClick(e) { 14 | e.preventDefault(); 15 | this.props.handleClick(); 16 | this.setState(prevState => ({ 17 | isToggleOn: !prevState.isToggleOn 18 | })); 19 | } 20 | 21 | render() { 22 | const { isToggleOn } = this.state; 23 | return ( 24 | 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Scale 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /components/icons/form/InputIcons.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const searchIcon = ( 4 | 11 | 16 | 17 | ); 18 | 19 | export const inputIconsList = { 20 | search: searchIcon 21 | }; 22 | 23 | export default inputIconsList; 24 | -------------------------------------------------------------------------------- /components/shared/form/InputSearch.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cn from 'classnames'; 3 | 4 | const InputSearch = ({ 5 | className, 6 | bg, 7 | icon, 8 | value, 9 | color, 10 | placeholder, 11 | border, 12 | onSearch, 13 | onFocus, 14 | onBlur, 15 | allowDarkMode, 16 | ...props 17 | }) => ( 18 |
25 | {icon ? {icon} : null} 26 | 36 |
37 | ); 38 | 39 | InputSearch.defaultProps = { 40 | bg: 'bg-white', 41 | placeholder: 'Search', 42 | onSearch: null, 43 | onFocus: null, 44 | onBlur: null, 45 | border: true, 46 | allowDarkMode: false 47 | }; 48 | 49 | export default InputSearch; 50 | -------------------------------------------------------------------------------- /pages/docs/[slug].js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Error from 'next/error' 3 | 4 | import { 5 | getDocEntry, 6 | getDocsEntries, 7 | getDocsCategories, 8 | getDocsSubcategories 9 | } from 'docs/lib/content/docs'; 10 | 11 | import DocsContainer from 'docs/containers/docs'; 12 | 13 | export async function getStaticPaths() { 14 | let pages = []; 15 | 16 | try { 17 | pages = await getDocsEntries(); 18 | } catch (error) { 19 | throw error; 20 | } 21 | 22 | let paths = []; 23 | 24 | if (pages) { 25 | paths = pages.map(page => ({ 26 | params: { slug: page.slug } 27 | })); 28 | } 29 | 30 | return { 31 | paths, 32 | fallback: true 33 | }; 34 | } 35 | 36 | export async function getStaticProps({ params }) { 37 | const props = {}; 38 | try { 39 | props.topLevelNavigation = await getDocsCategories(); 40 | props.subLevelNavigation = await getDocsSubcategories(); 41 | props.content = await getDocEntry(params.slug); 42 | props.slug = params.slug; 43 | } catch (error) { 44 | throw error; 45 | } 46 | 47 | return { props, revalidate: 1 }; 48 | } 49 | 50 | const Docs = ({ topLevelNavigation, subLevelNavigation, content, slug }) => { 51 | if (!content) { 52 | return ; 53 | } 54 | return ( 55 | 65 | ); 66 | }; 67 | 68 | Docs.defaultProps = { 69 | slug: null 70 | }; 71 | 72 | export default Docs; 73 | -------------------------------------------------------------------------------- /components/shared/tab-selector/Selector/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cn from 'classnames'; 3 | 4 | import { TabList } from 'react-aria-tabpanel'; 5 | 6 | import styles from './Selector.module.css'; 7 | 8 | const Selector = ({ 9 | children, 10 | selectorWidth, 11 | selectorLeft, 12 | opacity, 13 | centered, 14 | delayOnLoad, 15 | allowDarkMode 16 | }) => ( 17 | 18 |
25 |
36 |
46 |
    {children}
47 |
48 |
49 | 50 | ); 51 | 52 | Selector.defaultProps = { 53 | centered: true, // to center align the selector 54 | delayOnLoad: true, // to add delay to load animation 55 | allowDarkMode: false // to allow component to work with dark mode media queries 56 | }; 57 | 58 | export default Selector; 59 | -------------------------------------------------------------------------------- /components/shared/tab-selector/Item.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect } from 'react'; 2 | import cn from 'classnames'; 3 | 4 | import { Tab } from 'react-aria-tabpanel'; 5 | 6 | import styles from './Item.module.css'; 7 | 8 | const Item = ({ id, children, isActive, label, onClick, handleUrlFilter, textColors, selectorStyle }) => { 9 | const ref = useRef({ offsetWidth: 0, offsetLeft: 0 }); 10 | 11 | useEffect(() => { 12 | // because off css loading, will wait until it loads 13 | setTimeout(() => { 14 | if (isActive) { 15 | onClick(ref.current.offsetWidth, ref.current.offsetLeft); 16 | } 17 | }, 100); 18 | }, [isActive]); 19 | 20 | return ( 21 |
  • onClick(ref.current.offsetWidth, ref.current.offsetLeft)} 24 | > 25 | 26 |
    33 | {children} 34 |

    41 | {label} 42 |

    43 |
    44 |
    45 |
  • 46 | ); 47 | }; 48 | 49 | Item.defaultProps = { 50 | textColors: { 51 | active: 'text-black', 52 | inactive: 'text-gray-700' 53 | }, 54 | selectorStyle: 'normal' // to change styles for when you have multiple items 55 | }; 56 | 57 | export default Item; 58 | -------------------------------------------------------------------------------- /components/shared/Close/Close.module.css: -------------------------------------------------------------------------------- 1 | .navTrigger { 2 | width: 24px; 3 | height: 30px; 4 | position: relative; 5 | background: transparent; 6 | border: none; 7 | vertical-align: middle; 8 | margin: 0; 9 | padding: 0; 10 | cursor: pointer; 11 | display: block; 12 | } 13 | 14 | .navTrigger { 15 | @media only screen and (min-width: 992px) { 16 | display: none; 17 | } 18 | } 19 | 20 | .navTrigger:focus { 21 | outline: 0; 22 | } 23 | .navTrigger:hover span, 24 | .navTrigger:hover span:before, 25 | .navTrigger:hover span:after { 26 | @apply bg-gray-900; 27 | } 28 | 29 | .navTrigger:before { 30 | content: ''; 31 | opacity: 0; 32 | width: 0; 33 | height: 0; 34 | border-radius: 50%; 35 | position: absolute; 36 | top: 50%; 37 | left: 50%; 38 | background: transparent; 39 | transform: translate(-50%, -50%); 40 | transition: all 0.4s ease; 41 | } 42 | .navTrigger span { 43 | display: block; 44 | position: relative; 45 | } 46 | .navTrigger span:before, 47 | .navTrigger span:after { 48 | content: ''; 49 | position: absolute; 50 | left: 0; 51 | } 52 | .navTrigger span:before { 53 | top: -6px; 54 | } 55 | .navTrigger span:after { 56 | bottom: -6px; 57 | } 58 | .navTrigger span, 59 | .navTrigger span:before, 60 | .navTrigger span:after { 61 | width: 100%; 62 | height: 2px; 63 | @apply bg-gray-900; 64 | transition: all 0.4s ease; 65 | } 66 | 67 | .navTriggerOpen:before { 68 | opacity: 1; 69 | width: 20px; 70 | height: 20px; 71 | } 72 | .navTriggerOpen span { 73 | background: transparent !important; 74 | } 75 | .navTriggerOpen span:before { 76 | top: 0; 77 | transform: rotate(225deg); 78 | } 79 | .navTriggerOpen span:after { 80 | bottom: 0; 81 | transform: rotate(-225deg); 82 | } 83 | 84 | @screen dark { 85 | .navTrigger:hover span, 86 | .navTrigger:hover span:before, 87 | .navTrigger:hover span:after { 88 | @apply bg-white; 89 | } 90 | 91 | .navTrigger span, 92 | .navTrigger span:before, 93 | .navTrigger span:after { 94 | @apply bg-white; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /components/docs/SearchResults/SearchItem/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from 'next/link'; 3 | 4 | import styles from './SearchItem.module.css'; 5 | 6 | const Searchitem = ({ item }) => ( 7 |
  • 8 | {item.isReference ? ( 9 | 10 | 11 | 12 | ) : ( 13 | 14 | 15 | 16 | )} 17 |
  • 18 | ); 19 | 20 | const ExternalResult = ({ children, item }) => ( 21 | 26 | {children} 27 | 28 | ); 29 | 30 | const InternalResult = ({ children, item }) => ( 31 | 32 | 35 | {children} 36 | 37 | 38 | ); 39 | 40 | const Content = ({ item, showMethod }) => ( 41 | <> 42 |
    43 | {showMethod && item.method && ( 44 | 49 | {item.method} 50 | 51 | )} 52 | {item.title} 53 |
    54 | 58 | 59 | ); 60 | 61 | export default Searchitem; 62 | -------------------------------------------------------------------------------- /components/docs/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import cn from 'classnames'; 3 | import Link from 'next/link'; 4 | import StickyBox from 'react-sticky-box'; 5 | 6 | import Close from 'docs/components/shared/Close'; 7 | import Accordion from 'docs/components/docs/Accordion'; 8 | 9 | const Sidebar = ({ topLevelNavigation, subLevelNavigation, slug }) => { 10 | const [isOpen, setOpen] = useState(false); 11 | const [openHeaderMobile, setOpenHeaderMobile] = useState(false); 12 | 13 | const toggle = (index, show) => { 14 | if (!show) { 15 | setOpen(index); 16 | } else { 17 | setOpen(false); 18 | } 19 | }; 20 | 21 | const toggleHeaderMobile = () => { 22 | setOpenHeaderMobile(!openHeaderMobile); 23 | }; 24 | 25 | return ( 26 |
    27 |
    28 | 29 | Docs 30 | 31 |
    32 | 33 |
    34 |
    35 | 36 |
    37 |
    38 | 39 | 40 |

    Home

    41 |
    42 | 43 |
    44 | {topLevelNavigation && topLevelNavigation.map((item, i) => ( 45 | item.slug === slug)} 50 | slug={slug} 51 | index={i} 52 | toggle={toggle} 53 | /> 54 | ))} 55 |
    56 |
    57 |
    58 | ); 59 | }; 60 | 61 | export default Sidebar; 62 | -------------------------------------------------------------------------------- /components/docs/BodyContent/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { useDebounce } from 'react-use'; 3 | 4 | import InputSearch from 'docs/components/shared/form/InputSearch'; 5 | import SearchResults from 'docs/components/docs/SearchResults'; 6 | 7 | import { inputIconsList } from 'docs/components/icons/form/InputIcons'; 8 | 9 | import styles from './BodyContent.module.css'; 10 | 11 | const BodyContent = ({ children }) => { 12 | const [searchItems, updateSearchItems] = useState([]); 13 | const [searchQuery, updatesearchQuery] = useState(''); 14 | const [searchBox, updateSearchBox] = useState(false); 15 | 16 | const [] = useDebounce( 17 | async () => { 18 | if (!searchQuery.length) { 19 | updateSearchItems([]); 20 | } else { 21 | fetch(searchEndpoint(searchQuery)) 22 | .then(res => res.json()) 23 | .then(res => { 24 | updateSearchItems(res.results); 25 | }); 26 | } 27 | }, 28 | 100, 29 | [searchQuery] 30 | ); 31 | 32 | useEffect(() => { 33 | window.addEventListener('click', hideModal); 34 | return () => { 35 | window.removeEventListener('click', hideModal); 36 | }; 37 | }, []); 38 | 39 | const searchEndpoint = query => `/api/content/docs?search=${query}`; 40 | 41 | const onSearch = query => { 42 | updatesearchQuery(query); 43 | }; 44 | 45 | const handleModal = ev => { 46 | ev.stopPropagation(); 47 | updateSearchBox(true); 48 | }; 49 | 50 | const hideModal = () => { 51 | updateSearchBox(false); 52 | }; 53 | 54 | return ( 55 |
    56 |
    57 |
    58 | onSearch(ev.target.value)} 65 | onClick={ev => handleModal(ev)} 66 | allowDarkMode 67 | /> 68 |
    69 | {searchBox && } 70 | 71 |
    72 |
    {children}
    73 |
    74 | ); 75 | }; 76 | 77 | export default BodyContent; 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Custom Documentation Example! 2 | 3 | ![custom ui documentation](https://user-images.githubusercontent.com/18171127/96633996-44c2cc00-12f0-11eb-999b-ae9330d0a6c5.png) 4 | 5 | In this example you'll find an easy and slick way to implement your [Readme](https://readme.com/) content in a custom front-end using [Next.js](https://nextjs.org/) and [Tailwind](https://tailwindcss.com/). 6 | 7 | ## Getting Started 8 | 9 | ### Install 10 | 11 | ```bash 12 | yarn install 13 | # or 14 | npm install 15 | ``` 16 | 17 | ### Run 18 | 19 | ```bash 20 | yarn dev 21 | # or 22 | npm run dev 23 | ``` 24 | 25 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result, this will redirect you to [http://localhost:3000/docs](http://localhost:3000/docs) where you'll be able to read the instructions on how to connect and modify the example. 26 | 27 | ### Set up environment variables 28 | 29 | In the Readme dashboard go get your API KEY in the Configuration > API KEY tab. 30 | 31 | Next, copy the `.env.local.example` file in this directory to `.env.local` (which will be ignored by Git): 32 | 33 | ```bash 34 | cp .env.local.example .env.local 35 | ``` 36 | 37 | Then set each variable on `.env.local`: 38 | 39 | - `README_AUTH_TOKEN` should be the API token you just copied. 40 | 41 | Your `.env.local` file should look like this: 42 | 43 | ```bash 44 | README_AUTH_TOKEN=... 45 | ``` 46 | 47 | ## Learn More 48 | 49 | To learn more about Next.js, take a look at the following resources: 50 | 51 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 52 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 53 | 54 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 55 | 56 | To learn more about Tailwind, take a look at the following resources: 57 | 58 | - [Tailwind Documentation](https://tailwindcss.com/docs/installation) - learn about Tailwind customization features. 59 | 60 | To learn more about Readme, take a look at the following resources: 61 | 62 | - [Readme Documentation](https://docs.readme.com/developers/reference/intro) - learn about Readme features and API. 63 | 64 | 65 | ## Deploy on Vercel 66 | 67 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import/git?c=1&s=https://github.com/scaleapi/custom-documentation-example&env=README_AUTH_TOKEN&envDescription=Required%20to%20connect%20the%20app%20with%20ReadMe&envLink=https://github.com/scaleapi/custom-documentation-example%23set-up-environment-variables) from the creators of Next.js. 68 | 69 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 70 | -------------------------------------------------------------------------------- /lib/content/docs.js: -------------------------------------------------------------------------------- 1 | const requestConfig = { 2 | method: 'GET', 3 | headers: { 4 | 'content-type': 'application/json', 5 | 'x-readme-version': '1', 6 | Authorization: `Basic ${process.env.README_AUTH_TOKEN}` 7 | } 8 | }; 9 | 10 | async function getDocsCategories() { 11 | const result = await fetch('https://dash.readme.io/api/v1/categories', requestConfig); 12 | const json = await result.json(); 13 | if (!json.error) { 14 | return json.filter(category => !category.reference); 15 | } 16 | return false; 17 | } 18 | 19 | async function getDocsSubcategories() { 20 | const categories = await getDocsCategories(); 21 | if (categories) { 22 | const subcategoriesPromise = categories.map(subcategory => fetchSubcategory(subcategory.slug)); 23 | const subcategories = await Promise.all(subcategoriesPromise); 24 | return subcategories; 25 | }; 26 | return false; 27 | } 28 | 29 | async function getDocsEntries() { 30 | const categories = await getDocsCategories(); 31 | if (categories) { 32 | const subcategoriesPromise = categories.map(subcategory => fetchSubcategory(subcategory.slug)); 33 | const subcategories = await Promise.all(subcategoriesPromise); 34 | 35 | const entriesSlugs = subcategories.map(item => item.map(page => page.slug)); 36 | const entriesArray = [].concat.apply([], entriesSlugs); 37 | const entriesPromise = entriesArray.map(entry => fetchEntry(entry)); 38 | const entriesJson = await Promise.all(entriesPromise); 39 | return entriesJson; 40 | } 41 | return false; 42 | } 43 | 44 | async function getDocEntry(query = {}) { 45 | const result = await fetch(`https://dash.readme.io/api/v1/docs/${query}`, requestConfig); 46 | const json = await result.json(); 47 | return json; 48 | } 49 | 50 | async function getDocsSearch(search) { 51 | const result = await fetch(`https://dash.readme.io/api/v1/docs/search?search=${search}`, { 52 | method: 'POST', 53 | headers: { 54 | 'content-type': 'application/json', 55 | 'x-readme-version': '1', 56 | Authorization: `Basic ${process.env.README_AUTH_TOKEN}` 57 | } 58 | }); 59 | const json = await result.json(); 60 | return json; 61 | } 62 | 63 | const fetchSubcategory = async name => { 64 | const result = await fetch( 65 | `https://dash.readme.io/api/v1/categories/${name}/docs`, 66 | requestConfig 67 | ); 68 | return result.json(); 69 | }; 70 | 71 | const fetchEntry = async entry => { 72 | const result = await fetch(`https://dash.readme.io/api/v1/docs/${entry}`, requestConfig); 73 | 74 | const json = await result.json(); 75 | 76 | const parsed = { 77 | slug: json.slug, 78 | title: json.title, 79 | updatedAt: json.updatedAt 80 | }; 81 | 82 | return parsed; 83 | }; 84 | 85 | module.exports = { 86 | getDocsCategories, 87 | getDocsSubcategories, 88 | getDocsEntries, 89 | getDocsSearch, 90 | getDocEntry 91 | }; -------------------------------------------------------------------------------- /components/docs/Main/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import dayjs from 'dayjs'; 3 | import relativeTime from 'dayjs/plugin/relativeTime'; 4 | 5 | import Sidebar from 'docs/components/docs/Sidebar'; 6 | import BodyContent from 'docs/components/docs/BodyContent'; 7 | import IndexPage from 'docs/components/docs/IndexPage'; 8 | 9 | import styles from './Main.module.css'; 10 | 11 | const Main = ({ topLevelNavigation, subLevelNavigation, title, content, slug, updated }) => { 12 | dayjs.extend(relativeTime); 13 | return ( 14 |
    15 |
    16 | 21 | 22 | {content ? ( 23 |
    24 |
    25 | {title} 26 |
    27 |
    28 |
    29 | {calendarIcon} 30 | Updated {dayjs(updated).fromNow()} 31 |
    32 |
    33 | ) : ( 34 | 35 | )} 36 | 37 |
    38 |
    39 | ) 40 | } 41 | 42 | export const calendarIcon = ( 43 | 49 | 54 | 55 | ); 56 | 57 | export default Main; 58 | -------------------------------------------------------------------------------- /components/docs/Accordion.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import cn from 'classnames'; 3 | import Link from 'next/link'; 4 | 5 | const Accordion = ({ title, items, show, slug, index, toggle }) => { 6 | return ( 7 |
    8 |
    toggle(index, show)} 12 | > 13 | 14 | 18 | 19 | 20 | 21 |

    {title}

    22 |
    23 |
    27 | 73 |
    74 |
    75 | ); 76 | }; 77 | 78 | export default Accordion; 79 | -------------------------------------------------------------------------------- /components/docs/SearchResults/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | import { Wrapper, TabPanel } from 'react-aria-tabpanel'; 4 | import Selector from 'docs/components/shared/tab-selector/Selector'; 5 | import Item from 'docs/components/shared/tab-selector/Item'; 6 | 7 | import SearchItem from './SearchItem'; 8 | import styles from './SearchResults.module.css'; 9 | 10 | const SearchResults = ({ items, query }) => { 11 | const [activeTab, setActiveTab] = useState(tabs[0].id); 12 | const [selectorLeft, setSelectorLeft] = useState(0); 13 | const [selectorWidth, setSelectorWidth] = useState(0); 14 | 15 | const onClickSelector = (width, left) => { 16 | setSelectorWidth(width); 17 | setSelectorLeft(left); 18 | }; 19 | 20 | const onChange = id => { 21 | setActiveTab(id); 22 | }; 23 | 24 | return ( 25 | 26 |
    29 |
    ev.stopPropagation()}> 30 | 37 | {tabs.map(tab => ( 38 | 49 | ))} 50 | 51 |
    52 | {tabs.map(tab => ( 53 |
    54 | 55 | {activeTab === tab.id && ( 56 |
      57 | {tab.id === 'all' && items && 58 | items.map(item => )} 59 | {tab.id === 'documentation' && items && 60 | items 61 | .filter(item => !item.isReference) 62 | .map(item => )} 63 | {tab.id === 'api-reference' && items && 64 | items 65 | .filter(item => item.isReference) 66 | .map(item => )} 67 |
    68 | )} 69 |
    70 |
    71 | ))} 72 | {items && !items.length && ( 73 |
    74 | {query === '' && ( 75 |

    Start typing to search...

    76 | )} 77 | {query !== '' && ( 78 | <> 79 |

    Try a different search.

    80 |

    No results for `{query}`

    81 | 82 | )} 83 |
    84 | )} 85 |
    86 |
    87 | ); 88 | }; 89 | 90 | const tabs = [ 91 | { 92 | label: 'All', 93 | id: 'all', 94 | index: 0 95 | }, 96 | { 97 | label: 'Documentation', 98 | id: 'documentation', 99 | index: 1 100 | }, 101 | { 102 | label: 'API Reference', 103 | id: 'api-reference', 104 | index: 2 105 | } 106 | ]; 107 | 108 | export default SearchResults; 109 | -------------------------------------------------------------------------------- /components/docs/IndexPage/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const IndexPage = () => ( 4 | <> 5 |
    6 |
    7 |

    8 | Documentation 9 |

    10 |

    11 | Guides and quickstarts for integrating your company products. 12 |

    13 |
    14 |
    15 |
    16 | 17 |

    Welcome to your new documentation!

    18 |

    Thanks for trying out Scale's Custom Documentation Example! Here you'll find a quick setup guide to integrate your Readme documentation content with this example.

    19 |

    When you've finnished all of these steps remember to change the content of this page since it will be your home. You can do this in ./components/docs/IndexPage.

    20 | 21 |

    Connect with Readme

    22 |

    Go to Readme and create an account.

    23 |

    In the Readme dashboard go get your API KEY in the Configuration > API KEY tab.

    24 |

    After you've copied the API KEY go to the root folder and create an .env file and type README_AUTH_TOKEN=your-auth-token where your-auth-token is your API KEY.

    25 |

    If you already have created categories and pages under the categories in localhost:3000/docs you should be able to see the navigation populate with your categories and pages.

    26 |

    If you haven't done this then you won't get any navigation or pages so get to Readme and create those!

    27 | 28 |

    Where's my code at?

    29 |

    You'll be able to find all the functions used to retrieve the content and the search function from Readme in the ./lib/content/docs.js file.

    30 |

    On ./pages/api/content/docs.js you'll find where we store search results from the Algolia integration Readme has. You can find more information about API Routes in the Next.js API Routes documentation.

    31 |

    On the ./pages/docs folder you'll be able to find the home for docs and the [slug].js file which will be rendering any page under the category you've created it.

    32 |

    Last but not least in the ./components folder you'll find all the components we used to create this example. Feel free to customize anything you want!

    33 | 34 |

    How to style?

    35 |

    In the ./tailwind.config.js file you'll be able to see all the customizations we did for the Tailwind theme. There you'll be able to change fonts, colors, spacings, etc. More information about the Tailwind Theme on the Tailwind Theme documentation.

    36 |

    We also used CSS Modules for the custom CSS we wanted to add to the example, you can find this CSS in any .module.css file.

    37 |

    Good luck, have fun :)

    38 |
    39 | 40 | ); 41 | 42 | export default IndexPage; 43 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | future: { 3 | // removeDeprecatedGapUtilities: true, 4 | // purgeLayersByDefault: true, 5 | }, 6 | purge: [ 7 | './pages/**/*.js', 8 | './components/**/*.js', 9 | ], 10 | theme: { 11 | screens: { 12 | sm: '640px', 13 | md: '768px', 14 | lg: '992px' 15 | }, 16 | extend: { 17 | screens: { 18 | light: { raw: '(prefers-color-scheme: light)' }, 19 | dark: { raw: '(prefers-color-scheme: dark)' } 20 | } 21 | }, 22 | colors: { 23 | transparent: 'transparent', 24 | current: 'currentColor', 25 | black: '#000', 26 | white: '#fff', 27 | purple: { 28 | light: '#943CFF', 29 | dark: '#7F2CF3' 30 | }, 31 | pink: '#ED72E2', 32 | link: '#008EFF', 33 | gray: { 34 | 100: '#F7FAFC', 35 | 200: '#EDF2F7', 36 | 300: '#E2E8F0', 37 | 400: '#CBD5E0', 38 | 500: '#A0AEC0', 39 | 600: '#718096', 40 | 700: '#4A5568', 41 | 800: '#2D3748', 42 | 900: '#1A202C' 43 | }, 44 | utility: { 45 | blue: '#0076FF', 46 | red: '#EF3D57', 47 | gold: '#FFCA28', 48 | green: '#72CE7B' 49 | } 50 | }, 51 | spacing: { 52 | '0': '0', 53 | '1/2': '0.4rem', 54 | '1': '0.8rem', 55 | '2': '1.6rem', 56 | '3': '2.4rem', 57 | '4': '3.2rem', 58 | '5': '4rem', 59 | '6': '4.8rem', 60 | '7': '5.6rem', 61 | '8': '6.4rem', 62 | '9': '7.2rem', 63 | '10': '8rem', 64 | '11': '8.8rem', 65 | '12': '9.6rem', 66 | '13': '10.4rem', 67 | '14': '11.2rem', 68 | '15': '12rem', 69 | '16': '12.8rem', 70 | '17': '13.6rem', 71 | '18': '14.4rem', 72 | '19': '15.2rem', 73 | '20': '16rem', 74 | '21': '16.8rem', 75 | '22': '17.6rem', 76 | '23': '18.4rem', 77 | '24': '19.2rem', 78 | '25': '20rem' 79 | }, 80 | fontFamily: { 81 | maax: ['maax-rounded', '-apple-system', 'Arial', 'sans-serif'], 82 | body: ['Plex', '-apple-system', 'Arial', 'sans-serif'], 83 | code: ['Plex Mono', 'monospace', 'serif'] 84 | }, 85 | fontSize: { 86 | sm: ['1.4rem', '2.4rem'], 87 | base: ['1.6rem', '3.2rem'], 88 | lg: ['1.8rem', '3.2rem'], 89 | xl: ['2rem', '4rem'], 90 | '2xl': ['2.4rem', '4rem'], 91 | '3xl': ['3rem', '4.8rem'], 92 | '4xl': ['3.6rem', '5.6rem'], 93 | '5xl': ['4.8rem', '7.2rem'], 94 | '6xl': ['6.4rem', '9.6rem'] 95 | }, 96 | fontWeight: { 97 | normal: 400, 98 | medium: 500, 99 | bold: 700 100 | }, 101 | letterSpacing: { 102 | '03': '0.03rem', 103 | '1': '0.1rem', 104 | '2': '0.2rem', 105 | '3': '0.3rem', 106 | '4': '0.4rem' 107 | }, 108 | lineHeight: { 109 | '3': '2.4rem', 110 | '4': '3.2rem', 111 | '5': '4rem', 112 | '6': '4.8rem', 113 | '7': '5.6rem', 114 | '8': '6.4rem', 115 | '9': '7.2rem' 116 | }, 117 | borderRadius: { 118 | '0': '0', 119 | '1/2': '0.4rem', 120 | '1': '0.8rem', 121 | '2': '1.6rem', 122 | '3': '2.4rem', 123 | full: '100%' 124 | }, 125 | boxShadow: { 126 | base: '0px 1px 3px rgba(74, 74, 139, 0.1), 0px 1px 2px rgba(74, 74, 139, 0.06)', 127 | xl: '0px 10px 10px rgba(74, 74, 139, 0.04), 0px 20px 25px rgba(74, 74, 139, 0.1)', 128 | '2xl': '0px 25px 50px rgba(74, 74, 139, 0.25)', 129 | }, 130 | container: { 131 | center: true, 132 | padding: '1.6rem' 133 | }, 134 | opacity: { 135 | '0': '0', 136 | '10': '0.1', 137 | '20': '0.2', 138 | '30': '0.3', 139 | '40': '0.4', 140 | '50': '0.5', 141 | '60': '0.6', 142 | '70': '0.7', 143 | '80': '0.8', 144 | '90': '0.9', 145 | '100': '1' 146 | }, 147 | maxWidth: { 148 | none: 'none', 149 | '1/4': '25%', 150 | '1/2': '50%', 151 | '3/4': '75%' 152 | }, 153 | transitionDuration: { 154 | '0': '0ms', 155 | '150': '150ms', 156 | '250': '250ms', 157 | '375': '375ms', 158 | '500': '500ms', 159 | '750': '750ms', 160 | '1000': '1000ms', 161 | '1500': '1500ms', 162 | '2000': '2000ms' 163 | }, 164 | transitionDelay: { 165 | '0': '0ms', 166 | '150': '150ms', 167 | '250': '250ms', 168 | '375': '375ms', 169 | '500': '500ms', 170 | '750': '750ms', 171 | '1000': '1000ms', 172 | '1500': '1500ms', 173 | '2000': '2000ms' 174 | }, 175 | cursor: { 176 | default: 'default', 177 | pointer: 'pointer', 178 | initial: 'initial' 179 | } 180 | }, 181 | variants: { 182 | backgroundColor: ['responsive', 'hover'], 183 | margin: ['responsive', 'first', 'last'], 184 | boxShadow: ['responsive', 'hover'], 185 | display: ['responsive', 'group-hover'], 186 | fill: ['responsive'], 187 | borderWidth: ['responsive', 'first', 'last'], 188 | padding: ['responsive', 'first', 'last'], 189 | position: ['responsive'], 190 | justifyContent: ['responsive', 'first', 'last'], 191 | gap: ['responsive'], 192 | gridColumn: ['responsive'], 193 | overflow: ['responsive'], 194 | zIndex: ['responsive', 'hover', 'focus'], 195 | translate: ['responsive', 'hover', 'group-hover'], 196 | scale: ['responsive', 'hover', 'group-hover'], 197 | opacity: ['responsive', 'hover', 'group-hover'] 198 | }, 199 | plugins: [ 200 | // eslint-disable-next-line func-names 201 | function ({ addComponents }) { 202 | addComponents({ 203 | '.container': { 204 | width: '100%', 205 | maxWidth: 'none', 206 | '@screen md': { 207 | maxWidth: '768px' 208 | }, 209 | '@screen lg': { 210 | maxWidth: '1172px' 211 | } 212 | }, 213 | }); 214 | } 215 | ] 216 | }; 217 | -------------------------------------------------------------------------------- /components/docs/Main/Main.module.css: -------------------------------------------------------------------------------- 1 | .mainContent { 2 | position: relative; 3 | background: white; 4 | } 5 | 6 | @media (min-width: 992px) { 7 | .mainContent { 8 | background: linear-gradient(90deg, #f7fafc 50%, #ffffff 50%); 9 | } 10 | } 11 | 12 | @media (min-width: 992px) and (prefers-color-scheme: dark) { 13 | .mainContent { 14 | background: linear-gradient(90deg, #000000 50%, #1a202c 50%); 15 | } 16 | } 17 | 18 | .mainContent::before { 19 | content: ''; 20 | height: 1px; 21 | top: 79px; 22 | @apply absolute w-full bg-gray-200; 23 | } 24 | 25 | @screen dark { 26 | .mainContent { 27 | @apply bg-gray-900; 28 | } 29 | 30 | .mainContent::before { 31 | @apply bg-gray-800; 32 | } 33 | } 34 | 35 | .mainGrid { 36 | display: grid; 37 | } 38 | 39 | @media (min-width: 992px) { 40 | .mainGrid { 41 | grid-template-columns: 290px 1fr; 42 | } 43 | } 44 | 45 | /* CONTENT */ 46 | 47 | .content div[id*='plans'] { 48 | @apply flex my-4; 49 | } 50 | 51 | .content div[class*='planWrapper'] { 52 | @apply flex items-center m-0 mr-4; 53 | } 54 | 55 | .content p, 56 | .content h1, 57 | .content h2, 58 | .content h3, 59 | .content h4 { 60 | @apply mb-2; 61 | } 62 | 63 | .content a { 64 | @apply text-link; 65 | } 66 | 67 | .content [class*='header-scroll'] a { 68 | @apply h-2 absolute transition duration-250 ease-in-out; 69 | background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNiAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xMS4yNDk4IDYuNUMxMS4zODc5IDYuNSAxMS40OTk4IDYuNjExOTMgMTEuNDk5OCA2Ljc1VjcuMjVDMTEuNDk5OCA3LjM4ODA3IDExLjM4NzkgNy41IDExLjI0OTggNy41SDguNjEzNDVDOC41NTAyMSA3LjUwNTk4IDguNTAxNzUgNy41NTg4NSA4LjUwMTMgNy42MjIzOFYxMi4zMjYyVjEyLjMyNjJDOC41MDE0NSAxMi4zOTY0IDguNTU4NTEgMTIuNDUzMyA4LjYyODc2IDEyLjQ1MzFDOC42MzM1NCAxMi40NTMxIDguNjM4MzIgMTIuNDUyOCA4LjY0MzA3IDEyLjQ1MjNDOS43NDg0MyAxMi4zMDk5IDEwLjc3NDIgMTEuODAxNCAxMS41NTY4IDExLjAwNzhMMTEuMDc4OCAxMC41Mjk0QzEwLjk3NzkgMTAuNDI4NSAxMC45Nzc5IDEwLjI2NDkgMTEuMDc4OSAxMC4xNjRDMTEuMTE2NSAxMC4xMjY1IDExLjE2NDcgMTAuMTAxNCAxMS4yMTcxIDEwLjA5MjNMMTMuMTk5IDkuNzQ1NjVDMTMuMzQgOS43MjA5MiAxMy40NzQzIDkuODE1MTEgMTMuNDk5IDkuOTU2MDRDMTMuNTA0MiA5Ljk4NTU5IDEzLjUwNDIgMTAuMDE1OCAxMy40OTkgMTAuMDQ1NEwxMy4xNTI5IDEyLjAyNzVDMTMuMTI4MyAxMi4xNjg2IDEyLjk5MzkgMTIuMjYyOSAxMi44NTI5IDEyLjIzODNDMTIuODAwMyAxMi4yMjkxIDEyLjc1MTggMTIuMjAzOSAxMi43MTQxIDEyLjE2NjFMMTIuMjYxMiAxMS43MTI5QzExLjY5MSAxMi4yODk3IDExLjAxMDMgMTIuNzQ1NSAxMC4yNTk4IDEzLjA1MjlDOS40ODY0IDEzLjM2NTkgOC43ODMxNiAxMy44Mjk4IDguMTkxMTUgMTQuNDE3N0M4LjA4NjI0IDE0LjUyMjYgNy45MTYyMSAxNC41MjI2IDcuODExMyAxNC40MTc3QzcuMjE4ODQgMTMuODI5NiA2LjUxNTE0IDEzLjM2NTQgNS43NDEzIDEzLjA1MjNDNC45OTI2NiAxMi43NDU0IDQuMzEzNDggMTIuMjkxIDMuNzQ0MyAxMS43MTZMMy4yODg1MyAxMi4xNzM1QzMuMTg3NTYgMTIuMjc0OCAzLjAyMzUyIDEyLjI3NTIgMi45MjIxNCAxMi4xNzQyQzIuODg0MjEgMTIuMTM2NCAyLjg1ODkyIDEyLjA4NzkgMi44NDk3MyAxMi4wMzUxTDIuNTAzMzggMTAuMDQ1MUMyLjQ3ODg4IDkuOTA0MSAyLjU3MzM2IDkuNzY5OTIgMi43MTQzOCA5Ljc0NTQzQzIuNzQzODcgOS43NDAzMSAyLjc3NDAyIDkuNzQwMzMgMi44MDM1IDkuNzQ1NUw0Ljc4NSAxMC4wOTMyQzQuOTI2MDUgMTAuMTE3OSA1LjAyMDM2IDEwLjI1MjIgNC45OTU2NiAxMC4zOTMzQzQuOTg2NDkgMTAuNDQ1NiA0Ljk2MTQ2IDEwLjQ5MzkgNC45MjM5NSAxMC41MzE1TDQuNDQ3NzMgMTEuMDA5N0w0LjQ0NzczIDExLjAwOTdDNS4yMyAxMS44MDIyIDYuMjU1MDYgMTIuMzEgNy4zNTk1IDEyLjQ1MjJDNy40MjkyOSAxMi40NjAzIDcuNDkyMzkgMTIuNDEwMiA3LjUwMDQ0IDEyLjM0MDRDNy41MDA5OSAxMi4zMzU3IDcuNTAxMjcgMTIuMzMwOSA3LjUwMTI4IDEyLjMyNjFWNy42MjIzOEM3LjUwMDgzIDcuNTU4NzkgNy40NTIyNyA3LjUwNTg4IDcuMzg4OTUgNy41SDQuNzQ5ODNDNC42MTE3NiA3LjUgNC40OTk4MyA3LjM4ODA3IDQuNDk5ODMgNy4yNVY2Ljc1QzQuNDk5ODMgNi42MTE5MyA0LjYxMTc2IDYuNSA0Ljc0OTgzIDYuNUw3LjM3NjEgNi40OTc1QzcuNDQ1MTQgNi40OTc1IDcuNTAxMSA2LjQ0MTU0IDcuNTAxMSA2LjM3MjVWNS4wMDVWNS4wMDVDNy41MDA1OCA0Ljk1MjE5IDcuNDY2NjYgNC45MDU1MSA3LjQxNjYgNC44ODg3QzYuNTA1NDQgNC41NjU4NSA2LjAyODUzIDMuNTY1NDkgNi4zNTEzOCAyLjY1NDMzQzYuNjc0MjMgMS43NDMxNyA3LjY3NDU5IDEuMjY2MjYgOC41ODU3NSAxLjU4OTExQzkuNDk2OTEgMS45MTE5NiA5Ljk3MzgyIDIuOTEyMzIgOS42NTA5NyAzLjgyMzQ4QzkuNDc0NjggNC4zMjEwMSA5LjA4MzI4IDQuNzEyNDEgOC41ODU3NSA0Ljg4ODdDOC41MzU2OSA0LjkwNTUyIDguNTAxNzggNC45NTIyIDguNTAxMjUgNS4wMDVWNi4zNzI1QzguNTAxMjUgNi40NDE1NCA4LjU1NzIyIDYuNDk3NSA4LjYyNjI1IDYuNDk3NUwxMS4yNDk4IDYuNVpNOC4wMDEyMyAyLjQ5ODA4QzcuNTg3MDEgMi40OTgxNCA3LjI1MTI4IDIuODMzOTkgNy4yNTEzNSAzLjI0ODJDNy4yNTE0MiAzLjY2MjQxIDcuNTg3MjYgMy45OTgxNSA4LjAwMTQ4IDMuOTk4MDhDOC40MTU2OSAzLjk5ODAxIDguNzUxNDIgMy42NjIxNiA4Ljc1MTM1IDMuMjQ3OTVWMy4yNDgwOEM4Ljc1MTM1IDIuODMzODYgOC40MTU1NiAyLjQ5ODA4IDguMDAxMzUgMi40OTgwOEg4LjAwMTIzWiIgZmlsbD0iIzcxODA5NiIvPgo8L3N2Zz4K); 70 | bottom: 13px; 71 | left: -29px; 72 | padding-left: 29px; 73 | opacity: 0; 74 | background-repeat: no-repeat; 75 | line-height: 4rem; 76 | } 77 | 78 | .content [class*='header-scroll']:hover a:hover { 79 | opacity: 1; 80 | } 81 | 82 | .content [class*='header-scroll']:hover a { 83 | opacity: 0.8; 84 | @apply transition duration-250 ease-in-out; 85 | } 86 | 87 | /* FONT SIZES and COLORS */ 88 | 89 | .content p, 90 | .content ul, 91 | .content ol { 92 | @apply text-base text-gray-700; 93 | } 94 | 95 | .content h1 { 96 | @apply text-2xl; 97 | } 98 | 99 | .content h2 { 100 | @apply text-xl; 101 | } 102 | 103 | .content h3 { 104 | @apply text-lg; 105 | } 106 | 107 | .content h4 { 108 | @apply text-lg; 109 | } 110 | 111 | .content h1, 112 | .content h2, 113 | .content h3, 114 | .content h4 { 115 | @apply text-black; 116 | } 117 | 118 | .content h1[class*='header-scroll'], 119 | .content h2[class*='header-scroll'] { 120 | @apply relative pt-5 mt-6 border-t border-gray-300; 121 | } 122 | 123 | .content 124 | div[class*='magic-block-api-header']:first-of-type 125 | h1[class*='header-scroll']:first-of-type:nth-child(1), 126 | .content 127 | div[class*='magic-block-textarea']:first-of-type 128 | h1[class*='header-scroll']:first-of-type:nth-child(1), 129 | .content 130 | div[class*='magic-block-textarea']:first-of-type 131 | h2[class*='header-scroll']:first-of-type:nth-child(1) { 132 | @apply border-none pt-0 mt-0; 133 | } 134 | 135 | @screen dark { 136 | .content p, 137 | .content ul, 138 | .content ol { 139 | @apply text-gray-300; 140 | } 141 | 142 | .content h1[class*='header-scroll'], 143 | .content h2[class*='header-scroll'] { 144 | @apply border-gray-800; 145 | } 146 | 147 | .content h1, 148 | .content h2, 149 | .content h3, 150 | .content h4 { 151 | @apply text-white; 152 | } 153 | } 154 | 155 | /* LISTS */ 156 | 157 | .content ul, 158 | .content ol { 159 | @apply my-2 pb-2; 160 | } 161 | 162 | .content ul ul, 163 | .content ol ol, 164 | .content ol ul, 165 | .content ul ol { 166 | @apply pb-0; 167 | } 168 | 169 | .content ul:last-child, 170 | .content ul ul:last-child { 171 | @apply mb-0; 172 | } 173 | 174 | .content ol { 175 | list-style: decimal; 176 | @apply pl-3; 177 | } 178 | 179 | .content ol br { 180 | display: none; 181 | } 182 | 183 | .content ul { 184 | list-style: none; 185 | } 186 | 187 | .content ul li { 188 | @apply relative pl-3; 189 | } 190 | 191 | .content ul li::before { 192 | @apply bg-gray-600 w-1 h-1 absolute left-0 rounded-1; 193 | content: ''; 194 | top: 12px; 195 | } 196 | 197 | .content li:not(:last-child) { 198 | @apply mb-1; 199 | } 200 | 201 | @screen dark { 202 | .content ul li::before { 203 | @apply bg-gray-400; 204 | } 205 | } 206 | 207 | /* IMAGES */ 208 | 209 | .content div[class*='magic-block-image'] figure { 210 | @apply mb-5 pointer-events-none; 211 | } 212 | 213 | .content div[class*='magic-block-image'] img, 214 | .content video { 215 | @apply w-full overflow-hidden shadow-xl; 216 | } 217 | 218 | .content figcaption { 219 | @apply text-sm text-center; 220 | } 221 | 222 | .content iframe { 223 | @apply w-full mb-3; 224 | } 225 | 226 | /* BLOCKQUOTE */ 227 | 228 | .content blockquote:not(:last-child) { 229 | @apply mb-2; 230 | } 231 | 232 | .content div[class*='magic-block-callout'] { 233 | @apply bg-gray-100 p-3 pl-9 rounded-1 m-0; 234 | background-position: 24px 24px; 235 | background-repeat: no-repeat; 236 | } 237 | 238 | .content div[class*='magic-block-callout'] p, 239 | .content div[class*='magic-block-callout'] ul { 240 | @apply text-sm mt-0 mb-1; 241 | } 242 | 243 | .content div[class*='magic-block-callout'] ul { 244 | @apply mt-1; 245 | } 246 | 247 | .content div[class*='magic-block-callout'] ul li::before { 248 | top: 10px; 249 | } 250 | 251 | .content div[class*='magic-block-callout'] h3 { 252 | @apply text-base; 253 | } 254 | 255 | .content div[class*='magic-block-callout']:not(:last-child) { 256 | @apply mb-2; 257 | } 258 | 259 | .content div[class*='magic-block-callout type-success no-title'], 260 | .content div[class*='magic-block-callout type-warning no-title'], 261 | .content div[class*='magic-block-callout type-info no-title'] { 262 | padding-top: 2.9rem; 263 | } 264 | 265 | .content div[class*='magic-block-callout type-success'] { 266 | background-color: #F3FAF2; 267 | background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik02LjAwMzA4IDE1LjAxMDVIOC4wMTg4M0M4LjYzOTg2IDE1LjAxMDYgOS4yMjU3MSAxNC43MjIyIDkuNjA0NDggMTQuMjMwMUwxMy41OTA1IDkuMDUwMDZDMTMuODU5NSA4LjcwMDQ3IDE0LjAwNTQgOC4yNzE3NCAxNC4wMDU1IDcuODMwNjFWNC4wMTA2MVY0LjAxMDYxQzEzLjk5NzQgMy40NjY0IDE0LjQzMiAzLjAxODY4IDE0Ljk3NjMgMy4wMTA2MUMxNC45ODYxIDMuMDEwNDYgMTQuOTk1OSAzLjAxMDQ2IDE1LjAwNTggMy4wMTA2MUMxNi4yODkgMy4wMTA2MSAxOC4wMDY2IDQuNTEwNjEgMTguMDA2NiA3LjUxMDYxVjcuNTEwNjFDMTguMDcxNyA5LjI2MTExIDE3Ljc5MjEgMTEuMDA3MyAxNy4xODM5IDEyLjY1MDFDMTcuMTI0MSAxMi43NzU4IDE3LjE3NzYgMTIuOTI2MyAxNy4zMDM0IDEyLjk4NkMxNy4zMzU5IDEzLjAwMTUgMTcuMzcxMyAxMy4wMDk4IDE3LjQwNzMgMTMuMDEwNEgyMy45MjY2QzI0LjkzNjMgMTIuOTkzMyAyNS44MDkgMTMuNzExOSAyNS45ODU5IDE0LjcwNjJDMjYuMTEzMyAxNS41Mzk2IDI1LjcwMTQgMTYuMzYyOCAyNC45NTc5IDE2Ljc2MDRDMjUuOTI1NCAxNy4yNzY4IDI2LjI5MTEgMTguNDc5OCAyNS43NzQ3IDE5LjQ0NzNDMjUuNTM5IDE5Ljg4ODggMjUuMTQ1NCAyMC4yMjUgMjQuNjcyNSAyMC4zODg3QzI1LjAzNjIgMjAuNjQ1NCAyNS4zMDE5IDIxLjAxOCAyNS40MjYyIDIxLjQ0NTVWMjEuNDQ1NUMyNS41NTUxIDIxLjkyOTEgMjUuNDk3OSAyMi40NDM0IDI1LjI2NTkgMjIuODg2OEMyNC45OTg0IDIzLjUxNjUgMjQuMzk3IDIzLjk0MDIgMjMuNzE0IDIzLjk4MDFDMjMuOTI4OSAyNC4zMjgzIDI0LjAzMDEgMjQuNzM0NyAyNC4wMDM1IDI1LjE0MzFWMjUuMTQzMUMyMy44OTE1IDI2LjIyMDYgMjIuOTcyNCAyNy4wMzI0IDIxLjg4OTMgMjcuMDEwNkgxNC40Mjc2QzEyLjg0NzggMjcuMDEwNiAxMS4zMDM0IDI2LjU0MzEgOS45ODg3OCAyNS42NjdMOS41MDc4OCAyNS4zNDY1QzkuMTc5MjUgMjUuMTI3NSA4Ljc5MzE2IDI1LjAxMDYgOC4zOTgyMyAyNS4wMTA3SDYuMDAzMjNDNS40NTA5NSAyNS4wMTA4IDUuMDAzMSAyNC41NjMyIDUuMDAyOTMgMjQuMDExQzUuMDAyOTMgMjQuMDEwOSA1LjAwMjkzIDI0LjAxMDggNS4wMDI5MyAyNC4wMTA3VjE2LjAxMDdMNS4wMDI5MyAxNi4wMTA1QzUuMDAyOTMgMTUuNDU4MiA1LjQ1MDY0IDE1LjAxMDUgNi4wMDI5MyAxNS4wMTA1QzYuMDAzMDUgMTUuMDEwNSA2LjAwMzE2IDE1LjAxMDUgNi4wMDMyOCAxNS4wMTA1TDYuMDAzMDggMTUuMDEwNVoiIGZpbGw9IiMxNDVBMUIiLz4KPC9zdmc+Cg==); 268 | } 269 | 270 | .content div[class*='magic-block-callout type-success'] p, 271 | .content div[class*='magic-block-callout type-success'] ul { 272 | color: #2B5824; 273 | } 274 | 275 | .content div[class*='magic-block-callout type-success'] ul li::before { 276 | background: #2B5824; 277 | } 278 | 279 | .content div[class*='magic-block-callout type-success'] h3 { 280 | color: #2B5824; 281 | font-weight: 500; 282 | } 283 | 284 | .content div[class*='magic-block-callout type-warning'] { 285 | background-color: #FDFAE2; 286 | background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNi4wMTA1IDI3LjAxMDVDMTUuMjIyOSAyNyAxNC40Mzc5IDI2LjkxNjYgMTMuNjY1NiAyNi43NjE1QzEzLjMwMDggMjYuNjk5MSAxMi45MjY4IDI2Ljc5NiAxMi42MzgzIDI3LjAyNzZDMTEuMTYyMSAyOC4xMTIgOS40Mjg5OSAyOC43OTMxIDcuNjA5NTUgMjkuMDAzOUM3LjMxMDAyIDI5LjAzNTUgNy4wNDE2MiAyOC44MTgzIDcuMDEwMDcgMjguNTE4N0M2Ljk5NjU2IDI4LjM5MDUgNy4wMjg5IDI4LjI2MTcgNy4xMDEzNSAyOC4xNTUxVjI4LjE1NTFDNy44MDQ2MyAyNy4yNzk0IDguMzA5NTcgMjYuMjYxNiA4LjU4MTM1IDI1LjE3MThDOC42MzYyMyAyNC44NzQzIDguNTE5OTMgMjQuNTcxIDguMjgwMTUgMjQuMzg2NUM1LjYyNjQyIDIyLjQxNDkgNC4wNDY4NiAxOS4zMTYyIDQuMDEwNSAxNi4wMTA1QzQuMDEwNSA5Ljk0Mjg1IDkuMzgzMSA1LjAxMDUgMTYuMDEwNSA1LjAxMDVDMjIuNjM3OSA1LjAxMDUgMjguMDEwNSA5Ljk0Mjg1IDI4LjAxMDUgMTYuMDEwNUMyOC4wMTA1IDIyLjA3ODEgMjIuNjM3OCAyNy4wMTA1IDE2LjAxMDUgMjcuMDEwNVpNMTYuMDA3NSAyMy4wMTA1QzE2LjgzNTkgMjMuMDEwNSAxNy41MDc1IDIyLjMzODkgMTcuNTA3NSAyMS41MTA1QzE3LjUwNzUgMjAuNjgyMSAxNi44MzU5IDIwLjAxMDUgMTYuMDA3NSAyMC4wMTA1QzE1LjE3OTEgMjAuMDEwNSAxNC41MDc1IDIwLjY4MjEgMTQuNTA3NSAyMS41MTA1VjIxLjUxMDRDMTQuNTA3NSAyMi4zMzg5IDE1LjE3OSAyMy4wMTA0IDE2LjAwNzQgMjMuMDEwNEwxNi4wMDc1IDIzLjAxMDVaTTE3LjAwNjcgOS4wMTA1SDE1LjAxNzNDMTQuNzM4NCA5LjAwNzI0IDE0LjUwOTcgOS4yMzA2OCAxNC41MDY0IDkuNTA5NTZDMTQuNTA2MyA5LjUxODY0IDE0LjUwNjUgOS41Mjc3MiAxNC41MDY4IDkuNTM2OEwxNC45NjQyIDE4LjAzNjhDMTQuOTgxNiAxOC4zMDUyIDE1LjIwNTcgMTguNTEzMiAxNS40NzQ2IDE4LjUxMDVIMTYuNTQ5M0MxNi44MTgzIDE4LjUxMzIgMTcuMDQyNSAxOC4zMDUyIDE3LjA1OTkgMTguMDM2OEwxNy41MTcyIDkuNTM2OEMxNy41MjkgOS4yNTgxNCAxNy4zMTI3IDkuMDIyNyAxNy4wMzQgOS4wMTA5MUMxNy4wMjQ5IDkuMDEwNTMgMTcuMDE1OCA5LjAxMDM5IDE3LjAwNjcgOS4wMTA1VjkuMDEwNVoiIGZpbGw9IiM2NTRFMDkiLz4KPC9zdmc+Cg==); 287 | } 288 | 289 | .content div[class*='magic-block-callout type-warning'] p, 290 | .content div[class*='magic-block-callout type-warning'] ul { 291 | color: #654e09; 292 | } 293 | 294 | .content div[class*='magic-block-callout type-warning'] ul li::before { 295 | background: #654e09; 296 | } 297 | 298 | .content div[class*='magic-block-callout type-warning'] h3 { 299 | color: #654e09; 300 | font-weight: 500; 301 | } 302 | 303 | .content div[class*='magic-block-callout type-info'] { 304 | @apply bg-gray-100; 305 | background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xNi4wMDEyIDI3Ljk5MjJIMTYuMDAxMkM5LjM3MzgyIDI3Ljk5MjIgNC4wMDEyMiAyMi42MTk2IDQuMDAxMjIgMTUuOTkyMkM0LjAwMTIyIDkuMzY0NzkgOS4zNzM4MiAzLjk5MjE5IDE2LjAwMTIgMy45OTIxOUMyMi42Mjg2IDMuOTkyMTkgMjguMDAxMiA5LjM2NDc5IDI4LjAwMTIgMTUuOTkyMkMyOC4wMDEyIDIyLjYxOTYgMjIuNjI4NiAyNy45OTIyIDE2LjAwMTIgMjcuOTkyMkgxNi4wMDEyWk0xNi43NTEyIDExLjk5MjJDMTUuODU1NiAxMi4wNTU3IDE1LjA3NTQgMTEuMzg3IDE1LjAwMTIgMTAuNDkyMkMxNS4wNzU0IDkuNTk3MzMgMTUuODU1NiA4LjkyODYgMTYuNzUxMiA4Ljk5MjE5QzE3LjY0NjkgOC45Mjg2IDE4LjQyNzEgOS41OTczMyAxOC41MDEyIDEwLjQ5MjJDMTguNDI3MSAxMS4zODcxIDE3LjY0NjkgMTIuMDU1OCAxNi43NTEyIDExLjk5MjJIMTYuNzUxMlpNMTcuNDYxNSAxNS4zNzM5TDE2LjA2ODkgMjAuNjI4NlYyMC42Mjg2QzE1LjkzMzUgMjEuMTM4OSAxNi4yMDAxIDIxLjY3IDE2LjY5MDEgMjEuODY2NEMxNi44Nzk5IDIxLjk0MjQgMTcuMDA0MyAyMi4xMjYzIDE3LjAwNDIgMjIuMzMwN1YyMi40OTIyQzE3LjAwNDIgMjIuNzY4MiAxNi43ODA2IDIyLjk5MTkgMTYuNTA0NyAyMi45OTIySDE1LjAxMDFDMTQuMTgwNCAyMi45OTYyIDEzLjUwNDYgMjIuMzI2OSAxMy41MDA1IDIxLjQ5NzJDMTMuNDk5OSAyMS4zNjY3IDEzLjUxNjMgMjEuMjM2NyAxMy41NDkyIDIxLjExMDRMMTQuOTQxOCAxNS44NTU3QzE1LjA3NzEgMTUuMzQ1NCAxNC44MTA2IDE0LjgxNDMgMTQuMzIwNSAxNC42MTc5QzE0LjEzMDcgMTQuNTQxOSAxNC4wMDYzIDE0LjM1OCAxNC4wMDY0IDE0LjE1MzZWMTMuOTkyMlYxMy45OTIyQzE0LjAwNjQgMTMuNzE2MiAxNC4yMyAxMy40OTI0IDE0LjUwNiAxMy40OTIySDE2LjAwMDRDMTYuODMwMSAxMy40ODgxIDE3LjUwNiAxNC4xNTc0IDE3LjUxMDIgMTQuOTg3MUMxNy41MTA4IDE1LjExNzYgMTcuNDk0NCAxNS4yNDc2IDE3LjQ2MTUgMTUuMzczOUwxNy40NjE1IDE1LjM3MzlaIiBmaWxsPSIjNEE1NTY4Ii8+Cjwvc3ZnPgo=); 306 | } 307 | 308 | .content div[class*='magic-block-callout type-info'] p, 309 | .content div[class*='magic-block-callout type-info'] ul { 310 | @apply text-gray-800; 311 | } 312 | 313 | .content div[class*='magic-block-callout type-info'] ul li::before { 314 | @apply bg-gray-800; 315 | } 316 | 317 | .content div[class*='magic-block-callout type-info'] h3 { 318 | @apply text-black; 319 | } 320 | 321 | @screen dark { 322 | .content div[class*='magic-block-callout type-info'] { 323 | @apply bg-gray-200; 324 | } 325 | } 326 | 327 | /* PRE AND CODE */ 328 | 329 | .content pre, 330 | .content code { 331 | background: #f5f4f9; 332 | line-height: 1.6; 333 | @apply mb-2 rounded-1 text-gray-700; 334 | padding: 2.4rem; 335 | overflow: auto; 336 | } 337 | 338 | .content code { 339 | font-size: 1.4rem; 340 | padding: 0; 341 | border-radius: 0; 342 | } 343 | --------------------------------------------------------------------------------