mobile responsiveness

This commit is contained in:
MapleLeaf
2021-12-30 22:49:36 -06:00
parent a14120a2f8
commit 93cb788394
10 changed files with 409 additions and 35 deletions

View File

@@ -0,0 +1,47 @@
import {
CodeIcon,
DocumentTextIcon,
ExternalLinkIcon,
} from "@heroicons/react/solid"
import type { AppLinkProps } from "~/components/app-link"
import { createContentIndex } from "~/helpers/create-index.server"
import { inlineIconClass } from "~/styles"
export const mainLinks: AppLinkProps[] = [
{
type: "router",
to: "/docs/guides/getting-started",
label: (
<>
<DocumentTextIcon className={inlineIconClass} /> Guides
</>
),
},
{
type: "internal",
to: "/docs/api",
label: (
<>
<CodeIcon className={inlineIconClass} /> API Reference
</>
),
},
{
type: "external",
to: "https://github.com/itsMapleLeaf/reacord",
label: (
<>
<ExternalLinkIcon className={inlineIconClass} /> GitHub
</>
),
},
]
export async function getGuideLinks(): Promise<AppLinkProps[]> {
const entries = await createContentIndex("app/routes/docs/guides")
return entries.map((entry) => ({
type: "router",
label: entry.title,
to: entry.route,
}))
}

View File

@@ -0,0 +1,34 @@
import { Link } from "remix"
import { ExternalLink } from "~/components/external-link"
export type AppLinkProps = {
type: "router" | "internal" | "external"
label: React.ReactNode
to: string
className?: string
}
export function AppLink(props: AppLinkProps) {
switch (props.type) {
case "router":
return (
<Link className={props.className} to={props.to}>
{props.label}
</Link>
)
case "internal":
return (
<a className={props.className} href={props.to}>
{props.label}
</a>
)
case "external":
return (
<ExternalLink className={props.className} href={props.to}>
{props.label}
</ExternalLink>
)
}
}

View File

@@ -1,29 +1,102 @@
import { CodeIcon } from "@heroicons/react/outline"
import { DocumentTextIcon, ExternalLinkIcon } from "@heroicons/react/solid"
import { MenuAlt4Icon } from "@heroicons/react/outline"
import { useRect } from "@reach/rect"
import clsx from "clsx"
import { useRef, useState } from "react"
import { FocusOn } from "react-focus-on"
import { Link } from "remix"
import { ExternalLink } from "~/components/external-link"
import { mainLinks } from "~/app-links"
import { AppLink } from "~/components/app-link"
import type { ContentIndexEntry } from "~/helpers/create-index.server"
import { linkClass } from "~/styles"
export function MainNavigation() {
const menuItemClass = clsx`
px-3 py-2 transition text-left font-medium block
opacity-50 hover:opacity-100 hover:bg-black/30
`
export function MainNavigation({
guideRoutes,
}: {
guideRoutes: ContentIndexEntry[]
}) {
return (
<nav className="flex justify-between items-center h-16">
<Link to="/">
<h1 className="text-3xl font-light">reacord</h1>
</Link>
<div className="flex gap-4">
<Link className={linkClass} to="/docs/guides/getting-started">
<DocumentTextIcon className="inline align-sub w-5" /> Guides
</Link>
<a className={linkClass} href="/docs/api">
<CodeIcon className="inline align-sub w-5" /> API Reference
</a>
<ExternalLink
className={linkClass}
href="https://github.com/itsMapleLeaf/reacord"
>
<ExternalLinkIcon className="inline align-sub w-5" /> GitHub
</ExternalLink>
<div className="hidden md:flex gap-4">
{mainLinks.map((link) => (
<AppLink key={link.to} className={linkClass} {...link} />
))}
</div>
<div className="md:hidden">
<PopoverMenu>
{mainLinks.map((link) => (
<AppLink key={link.to} className={menuItemClass} {...link} />
))}
<hr className="border-0 h-[2px] bg-black/50" />
{guideRoutes.map((route) => (
<Link key={route.route} to={route.route} className={menuItemClass}>
{route.title}
</Link>
))}
</PopoverMenu>
</div>
</nav>
)
}
function PopoverMenu({ children }: { children: React.ReactNode }) {
const [visible, setVisible] = useState(false)
const buttonRef = useRef<HTMLButtonElement>(null)
const buttonRect = useRect(buttonRef)
const panelRef = useRef<HTMLDivElement>(null)
const panelRect = useRect(panelRef)
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
return (
<>
<button
title="Menu"
className={linkClass}
onClick={() => setVisible(!visible)}
ref={buttonRef}
>
<MenuAlt4Icon className="w-6" />
</button>
<FocusOn
enabled={visible}
onClickOutside={() => setVisible(false)}
onEscapeKey={() => setVisible(false)}
>
<div
className="fixed"
style={{
left: (buttonRect?.right ?? 0) - (panelRect?.width ?? 0),
top: (buttonRect?.bottom ?? 0) + 8,
}}
onClick={() => setVisible(false)}
>
<div
className={clsx(
"transition-all",
visible
? "opacity-100 visible"
: "translate-y-2 opacity-0 invisible",
)}
>
<div ref={panelRef}>
<div className="w-48 bg-slate-800 rounded-lg shadow overflow-hidden max-h-[calc(100vh-4rem)] overflow-y-auto">
{children}
</div>
</div>
</div>
</div>
</FocusOn>
</>
)
}

View File

@@ -20,11 +20,11 @@ export default function Docs() {
<>
<HeaderPanel>
<div className={maxWidthContainer}>
<MainNavigation />
<MainNavigation guideRoutes={data} />
</div>
</HeaderPanel>
<main className={clsx(maxWidthContainer, "mt-8 flex items-start gap-4")}>
<nav className="w-64 sticky top-24">
<nav className="w-48 sticky top-24 hidden md:block">
<h2 className="text-2xl">Guides</h2>
<ul className="mt-3 flex flex-col gap-2 items-start">
{data.map(({ title, route }) => (
@@ -49,7 +49,7 @@ function HeaderPanel({ children }: { children: React.ReactNode }) {
const className = clsx(
isScrolled ? "bg-slate-700/30" : "bg-slate-800",
"shadow-md sticky top-0 backdrop-blur-sm transition z-10 flex",
"shadow sticky top-0 backdrop-blur-sm transition z-10 flex",
)
return <header className={className}>{children}</header>

View File

@@ -1,25 +1,36 @@
import packageJson from "reacord/package.json"
import { Link } from "remix"
import type { LoaderFunction } from "remix"
import { Link, useLoaderData } from "remix"
import LandingExample from "~/components/landing-example.mdx"
import { MainNavigation } from "~/components/main-navigation"
import type { ContentIndexEntry } from "~/helpers/create-index.server"
import { createContentIndex } from "~/helpers/create-index.server"
import { maxWidthContainer } from "~/styles"
type LoaderData = ContentIndexEntry[]
export const loader: LoaderFunction = async () => {
const data: LoaderData = await createContentIndex("app/routes/docs/guides")
return data
}
export default function Landing() {
const data: LoaderData = useLoaderData()
return (
<div className="flex flex-col min-w-0 min-h-screen text-center">
<header className={maxWidthContainer}>
<MainNavigation />
<MainNavigation guideRoutes={data} />
</header>
<div className="px-4 pb-8 flex flex-1">
<main className="px-4 py-6 rounded-lg shadow-md bg-slate-800 space-y-5 m-auto w-full max-w-xl">
<main className="px-4 py-6 rounded-lg shadow bg-slate-800 space-y-5 m-auto w-full max-w-xl">
<h1 className="text-6xl font-light">reacord</h1>
<section className="mx-auto shadow text-sm sm:text-base">
<section className="mx-auto text-sm sm:text-base">
<LandingExample />
</section>
<p className="text-2xl font-light">{packageJson.description}</p>
<Link
to="/docs/guides/getting-started"
className="inline-block px-4 py-3 text-xl transition rounded-lg bg-emerald-700 hover:translate-y-[-2px] hover:bg-emerald-800 hover:shadow-md"
className="inline-block px-4 py-3 text-xl transition rounded-lg bg-emerald-700 hover:translate-y-[-2px] hover:bg-emerald-800 hover:shadow"
>
Get Started
</Link>

View File

@@ -1,8 +1,8 @@
import clsx from "clsx"
export const maxWidthContainer = clsx`
mx-auto w-full max-w-screen-lg px-4
`
export const maxWidthContainer = clsx`mx-auto w-full max-w-screen-lg px-4`
export const inlineIconClass = clsx`inline w-5 align-sub`
export const linkClass = clsx`
font-medium inline-block relative
@@ -13,12 +13,12 @@ export const linkClass = clsx`
export const docsProseClass = clsx`
prose prose-invert
prose-h1:font-light prose-h1:mb-4
prose-h1:font-light prose-h1:mb-4 prose-h1:text-3xl lg:prose-h1:text-4xl
prose-h2:font-light
prose-h3:font-light
prose-p:my-4
prose-a:font-medium prose-a:text-emerald-400 hover:prose-a:no-underline
prose-strong:font-medium prose-strong:text-emerald-400
prose-pre:text-[15px] prose-pre:font-monospace prose-pre:overflow-x-auto
prose-pre:font-monospace prose-pre:overflow-x-auto
max-w-none
`

View File

@@ -10,7 +10,9 @@
"start": "remix-serve build"
},
"dependencies": {
"@headlessui/react": "^1.4.2",
"@heroicons/react": "^1.0.5",
"@reach/rect": "^0.16.0",
"@remix-run/react": "^1.1.1",
"@remix-run/serve": "^1.1.1",
"@remix-run/server-runtime": "^1.1.1",
@@ -23,6 +25,7 @@
"reacord": "workspace:*",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-focus-on": "^3.5.4",
"rehype-stringify": "^9.0.2",
"remark-parse": "^10.0.1",
"remark-rehype": "^10.1.0",

View File

@@ -7,6 +7,9 @@ module.exports = {
sans: ["Rubik", "sans-serif"],
monospace: ["'JetBrains Mono'", "monospace"],
},
boxShadow: {
DEFAULT: "0 2px 9px 0 rgb(0 0 0 / 0.3), 0 2px 4px -2px rgb(0 0 0 / 0.3)",
},
extend: {},
},
plugins: [require("@tailwindcss/typography")],