forked from
standard.site/standard.site
Standard.site landing page built in Next.js
1'use client'
2
3import { useState, useRef, useEffect } from 'react'
4import { usePathname } from 'next/navigation'
5import { useDocsMarkdown, useDocsAtUri } from './DocsMarkdownContext'
6
7export function DocsPageMenu() {
8 const [open, setOpen] = useState(false)
9 const [copied, setCopied] = useState(false)
10 const menuRef = useRef<HTMLDivElement>(null)
11 const pathname = usePathname()
12 const markdown = useDocsMarkdown()
13 const atUri = useDocsAtUri()
14
15 const slug = (() => {
16 const path = pathname.split(/[?#]/)[0]
17 if (!path.startsWith('/docs/')) return null
18 const parts = path.split('/').filter(Boolean)
19 if (parts.length <= 1) return null
20 return parts.slice(1).join('/')
21 })()
22
23 useEffect(() => {
24 if (!open) return
25
26 function handleClickOutside(e: MouseEvent) {
27 if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
28 setOpen(false)
29 }
30 }
31
32 document.addEventListener('mousedown', handleClickOutside)
33 return () => document.removeEventListener('mousedown', handleClickOutside)
34 }, [open])
35
36 if (!slug) return null
37
38 const handleCopyMarkdown = async () => {
39 if (!markdown) return
40
41 try {
42 await navigator.clipboard.writeText(markdown)
43 setCopied(true)
44 setTimeout(() => {
45 setCopied(false)
46 setOpen(false)
47 }, 1500)
48 } catch {
49 setOpen(false)
50 }
51 }
52
53 const handleViewMarkdown = () => {
54 if (!markdown) return
55
56 const blob = new Blob([markdown], { type: 'text/plain' })
57 window.open(URL.createObjectURL(blob), '_blank')
58 setOpen(false)
59 }
60
61 return (
62 <div ref={menuRef} className="relative">
63 <button
64 onClick={() => setOpen(!open)}
65 className="flex items-center justify-center text-muted hover:text-base-content transition-colors p-1 rounded-md hover:bg-base-200 mt-1 size-8"
66 aria-label="Page options"
67 >
68 <svg className="size-4" fill="currentColor" viewBox="0 0 20 20">
69 <path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" />
70 </svg>
71 </button>
72
73 {open && (
74 <div className="absolute right-0 top-full mt-2 p-0.5 bg-base-200 border border-border grid gap-px rounded-xl shadow-lg z-20 min-w-48">
75 <button
76 onClick={handleCopyMarkdown}
77 className="w-full flex items-center gap-2 px-3 py-2 text-sm text-muted hover:text-base-content hover:bg-base-300 rounded-lg transition-colors"
78 >
79 {copied ? (
80 <>
81 <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
82 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
83 </svg>
84 Copied!
85 </>
86 ) : (
87 <>
88 <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
89 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
90 </svg>
91 Copy as Markdown
92 </>
93 )}
94 </button>
95 <button
96 onClick={handleViewMarkdown}
97 className="w-full flex items-center gap-2 px-3 py-2 text-sm text-muted hover:text-base-content hover:bg-base-300 rounded-lg transition-colors"
98 >
99 <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
100 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
101 </svg>
102 View raw Markdown
103 </button>
104 {atUri && (
105 <a
106 href={`https://pdsls.dev/${atUri}`}
107 target="_blank"
108 rel="noopener noreferrer"
109 onClick={() => setOpen(false)}
110 className="w-full flex items-center gap-2 px-3 py-2 text-sm text-muted hover:text-base-content hover:bg-base-300 rounded-lg transition-colors"
111 >
112 <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
113 <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
114 </svg>
115 View on pdsls.dev
116 </a>
117 )}
118 </div>
119 )}
120 </div>
121 )
122}