Skip to content

Commit

Permalink
feat: more aria (#329)
Browse files Browse the repository at this point in the history
* menu and search dialogs

* search tag

* -MenuContext

* cmdk

* cmdk overflow

* arrow keys nav
  • Loading branch information
abernier authored Sep 13, 2024
1 parent f3f86b7 commit 4d7ffd3
Show file tree
Hide file tree
Showing 11 changed files with 865 additions and 229 deletions.
698 changes: 698 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@
"dependencies": {
"@codesandbox/sandpack-react": "^2.19.0",
"@radix-ui/react-collapsible": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-visually-hidden": "^1.1.0",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/typography": "^0.5.14",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"gray-matter": "^4.0.3",
"image-size": "^1.1.1",
"match-sorter": "^6.3.4",
Expand Down
16 changes: 4 additions & 12 deletions src/app/[...slug]/Burger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,11 @@
import Icon from '@/components/Icon'
import cn from '@/lib/cn'
import { ComponentProps } from 'react'
import { useMenu } from './MenuContext'

export function Burger({ className }: ComponentProps<'button'>) {
const [menuOpen, setMenuOpen] = useMenu()

export function Burger({ opened, className }: { opened: boolean } & ComponentProps<'span'>) {
return (
<button
className={cn(className, 'flex size-9 items-center justify-center')}
type="button"
aria-label="Menu"
onClick={() => setMenuOpen(!menuOpen)}
>
{menuOpen ? <Icon icon="close" /> : <Icon icon="menu" />}
</button>
<span className={cn(className, 'flex size-9 items-center justify-center')} aria-label="Menu">
{opened ? <Icon icon="close" /> : <Icon icon="menu" />}
</span>
)
}
37 changes: 17 additions & 20 deletions src/app/[...slug]/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,25 @@
'use client'

import { Nav } from '@/components/Nav'
import cn from '@/lib/cn'
import { ComponentProps, ElementRef, useEffect, useRef } from 'react'
import { useDocs } from './DocsContext'
import { useMenu } from './MenuContext'
import * as Dialog from '@radix-ui/react-dialog'
import * as VisuallyHidden from '@radix-ui/react-visually-hidden'
import { ComponentProps, useState } from 'react'

export function Menu({ className, asPath }: ComponentProps<'dialog'> & { asPath: string }) {
const { doc, docs } = useDocs()
import { Burger } from './Burger'

const [opened, setOpened] = useMenu()
const dialogRef = useRef<ElementRef<'dialog'>>(null)

useEffect(() => {
if (opened) {
dialogRef.current?.show()
} else {
dialogRef.current?.close()
}
}, [opened])
export function Menu({ children, ...props }: ComponentProps<typeof Dialog.Content>) {
const [opened, setOpened] = useState(false)

return (
<dialog ref={dialogRef} className={cn(className, 'bg-surface-dim/95 backdrop-blur-xl')}>
<Nav docs={docs} asPath={asPath} collapsible={false} />
</dialog>
<Dialog.Root open={opened} onOpenChange={setOpened}>
<Dialog.Trigger>
<Burger opened={opened} className="lg:hidden" />
</Dialog.Trigger>
<Dialog.Content {...props}>
<VisuallyHidden.Root>
<Dialog.Title>Menu</Dialog.Title>
</VisuallyHidden.Root>
{children}
</Dialog.Content>
</Dialog.Root>
)
}
30 changes: 0 additions & 30 deletions src/app/[...slug]/MenuContext.tsx

This file was deleted.

40 changes: 17 additions & 23 deletions src/app/[...slug]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,8 @@ import { getData } from '@/utils/docs'
import Link from 'next/link'
import { PiDiscordLogoLight } from 'react-icons/pi'
import { VscGithubAlt } from 'react-icons/vsc'
import { Burger } from './Burger'
import { DocsContext } from './DocsContext'
import { Menu } from './Menu'
import { MenuContext } from './MenuContext'

export type Props = {
params: { slug: string[] }
Expand Down Expand Up @@ -52,7 +50,7 @@ export default async function Layoutt({ params, children }: Props) {
</span>
</div>

<Search />
<Search className="grow" />

<div className="flex">
{[
Expand All @@ -73,7 +71,9 @@ export default async function Layoutt({ params, children }: Props) {
))}
{/* <ToggleTheme className="hidden size-9 items-center justify-center sm:flex" /> */}

<Burger className="lg:hidden" />
<Menu className="z-100 bg-surface absolute inset-0 top-[--header-height] h-[calc(100dvh-var(--header-height))] w-full overflow-auto lg:hidden">
<Nav docs={docs} asPath={asPath} collapsible={false} />
</Menu>
</div>
</div>
)
Expand Down Expand Up @@ -158,25 +158,19 @@ export default async function Layoutt({ params, children }: Props) {
return (
<>
<DocsContext value={{ docs, doc }}>
<MenuContext>
<Layout className="[--side-w:theme(spacing.72)]">
<LayoutHeader className="z-10 border-b border-outline-variant/50 bg-surface/95 backdrop-blur-xl">
{header}
<Menu
asPath={asPath}
className="z-100 left-0 top-[--header-height] h-[calc(100dvh-var(--header-height))] w-full overflow-auto lg:hidden"
/>
</LayoutHeader>
<LayoutContent className="lg:mr-[--rgrid-m] xl:mr-0">
<article className="post-container">
{children}
{footer}
</article>
</LayoutContent>
<LayoutNav className="pt-8">{nav}</LayoutNav>
<LayoutAside className="pt-8">{toc}</LayoutAside>
</Layout>
</MenuContext>
<Layout className="[--side-w:theme(spacing.72)]">
<LayoutHeader className="z-10 border-b border-outline-variant/50 bg-surface/95 backdrop-blur-xl">
{header}
</LayoutHeader>
<LayoutContent className="lg:mr-[--rgrid-m] xl:mr-0">
<article className="post-container">
{children}
{footer}
</article>
</LayoutContent>
<LayoutNav className="pt-8">{nav}</LayoutNav>
<LayoutAside className="pt-8">{toc}</LayoutAside>
</Layout>
</DocsContext>
</>
)
Expand Down
4 changes: 2 additions & 2 deletions src/components/Nav/NavCategory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ function NavItem({
<Link
{...props}
className={cn(
'block cursor-pointer p-3 pl-8 focus:outline-none',
active ? 'interactive-bg-primary-container' : 'interactive-bg-surface',
'block cursor-pointer p-3 pl-8',
active ? 'bg-primary-container' : 'interactive-bg-surface',
className,
)}
>
Expand Down
46 changes: 25 additions & 21 deletions src/components/Search/SearchItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Icon from '@/components/Icon'
import cn from '@/lib/cn'
import { highlight } from '@/utils/text'
import Link from 'next/link'
import { ComponentProps } from 'react'
import sanitizeHtml from 'sanitize-html'

export interface SearchResult {
Expand All @@ -24,38 +26,40 @@ function sanitizeAllHtmlButMark(str: string) {
})
}

function SearchItem({ search, result }: SearchItemProps) {
function SearchItem({
className,
search,
result,
}: Omit<ComponentProps<typeof Link>, 'href'> & SearchItemProps) {
return (
<Link
href={result.url}
className="search-item block no-underline outline-none"
className={cn(className, 'block no-underline')}
target={result.url.startsWith('http') ? '_blank' : undefined}
>
<li className="px-2 py-1">
<div className="interactive-bg-surface-container-high flex items-center justify-between rounded-md p-4 py-5 transition-colors">
<div className="break-all pr-3">
<div className="block pb-1 text-xs text-on-surface-variant/50">{result.label}</div>
<div className="flex items-center justify-between rounded-md p-4 py-5">
<div className="break-all pr-3">
<div className="block pb-1 text-xs text-on-surface-variant/50">{result.label}</div>
<span
dangerouslySetInnerHTML={{
__html: highlight(sanitizeAllHtmlButMark(result.title), search),
}}
/>
<div className="block pt-2 text-sm text-on-surface-variant/50">
<span
dangerouslySetInnerHTML={{
__html: highlight(sanitizeAllHtmlButMark(result.title), search),
__html: highlight(sanitizeAllHtmlButMark(result.content), search),
}}
/>
<div className="block pt-2 text-sm text-on-surface-variant/50">
<span
dangerouslySetInnerHTML={{
__html: highlight(sanitizeAllHtmlButMark(result.content), search),
}}
/>
</div>
</div>
{result.image ? (
// eslint-disable-next-line @next/next/no-img-element
<img className="max-w-[40%] rounded" src={result.image} alt={result.title} />
) : (
<Icon icon="enter" />
)}
</div>
</li>
{result.image ? (
// eslint-disable-next-line @next/next/no-img-element
<img className="max-w-[40%] rounded" src={result.image} alt={result.title} />
) : (
<Icon icon="enter" />
)}
</div>
</Link>
)
}
Expand Down
50 changes: 0 additions & 50 deletions src/components/Search/SearchModal.tsx

This file was deleted.

62 changes: 45 additions & 17 deletions src/components/Search/SearchModalContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,16 @@ import * as React from 'react'

import { useDocs } from '@/app/[...slug]/DocsContext'

import { escape } from '@/utils/text'
import cn from '@/lib/cn'
import { Command } from 'cmdk'
import { useRouter } from 'next/navigation'
import { ComponentProps } from 'react'
import type { SearchResult } from './SearchItem'
import SearchModal from './SearchModal'
import SearchItem from './SearchItem'

export interface SearchModalContainerProps {
onClose: React.MouseEventHandler<HTMLButtonElement>
}

export const SearchModalContainer = ({ onClose }: SearchModalContainerProps) => {
// const router = useRouter()
// const boxes = useCSB()
export const SearchModalContainer = ({ className }: ComponentProps<'search'>) => {
const router = useRouter()
const { docs } = useDocs()
// console.log('docs', docs)
// const [lib] = router.query.slug as string[]
const [query, setQuery] = React.useState('')
const deferredQuery = React.useDeferredValue(query)
const [results, setResults] = React.useState<SearchResult[]>([])
Expand Down Expand Up @@ -62,11 +58,43 @@ export const SearchModalContainer = ({ onClose }: SearchModalContainerProps) =>
}, [docs, deferredQuery])

return (
<SearchModal
search={query}
results={results}
onClose={onClose}
onChange={(e) => setQuery(escape(e.target.value))}
/>
<search
className={cn(
'[--Search-Input-height:theme(spacing.16)]',
'mt-[--Search-Input-top]',
className,
)}
>
<Command shouldFilter={false} className="">
<Command.Input
name="search"
id="search"
className="bg-surface-container block h-[--Search-Input-height] w-full rounded-md px-4 pl-10 sm:text-sm"
placeholder="Search the docs"
value={query}
autoFocus
onValueChange={(value) => setQuery(value)}
/>

<Command.List>
{results.length > 0 && (
<div className="bg-surface-container mt-1 flex max-h-[calc((100dvh-var(--Search-Input-top)-1.5rem)-var(--Search-Input-height))] flex-col gap-1 overflow-auto rounded-md p-1">
{results.map((result, index) => {
return (
<Command.Item
key={`search-item-${index}`}
value={result.url}
onSelect={router.push}
className="rounded-md transition-colors data-[selected=true]:bg-surface-container-high"
>
<SearchItem search={query} result={result} />
</Command.Item>
)
})}
</div>
)}
</Command.List>
</Command>
</search>
)
}
Loading

0 comments on commit 4d7ffd3

Please sign in to comment.