Skip to content

Commit 201abac

Browse files
committed
add legal modal and link to personal web
1 parent 2e8211c commit 201abac

5 files changed

Lines changed: 351 additions & 0 deletions

File tree

‎src/App.css‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,12 @@
231231
color: var(--accent);
232232
}
233233

234+
.topbar-menu-sections {
235+
display: flex;
236+
flex-direction: column;
237+
gap: 20px;
238+
}
239+
234240
.topbar-menu-section h3 {
235241
font-size: 11px;
236242
text-transform: uppercase;
@@ -2125,6 +2131,74 @@
21252131
text-transform: capitalize;
21262132
}
21272133

2134+
.statusbar-legal {
2135+
display: inline-flex;
2136+
align-items: center;
2137+
gap: 4px;
2138+
flex-shrink: 0;
2139+
}
2140+
2141+
.statusbar-legal-link {
2142+
padding: 0;
2143+
font: inherit;
2144+
font-size: 11px;
2145+
color: var(--text);
2146+
background: none;
2147+
border: none;
2148+
cursor: pointer;
2149+
text-decoration: underline;
2150+
text-underline-offset: 2px;
2151+
}
2152+
2153+
a.statusbar-legal-link {
2154+
text-decoration: underline;
2155+
}
2156+
2157+
.statusbar-legal-link:hover {
2158+
color: var(--text-h);
2159+
}
2160+
2161+
.statusbar-legal-sep {
2162+
color: var(--border);
2163+
font-size: 11px;
2164+
user-select: none;
2165+
}
2166+
2167+
.modal-legal {
2168+
max-width: 560px;
2169+
max-height: min(80vh, 720px);
2170+
}
2171+
2172+
.legal-body {
2173+
display: flex;
2174+
flex-direction: column;
2175+
gap: 16px;
2176+
overflow-y: auto;
2177+
font: 13px/1.5 var(--sans);
2178+
color: var(--text);
2179+
}
2180+
2181+
.legal-updated {
2182+
margin: 0;
2183+
font-size: 12px;
2184+
color: var(--text);
2185+
opacity: 0.8;
2186+
}
2187+
2188+
.legal-section h3 {
2189+
margin: 0 0 6px;
2190+
font-size: 14px;
2191+
color: var(--text-h);
2192+
}
2193+
2194+
.legal-section p {
2195+
margin: 0 0 8px;
2196+
}
2197+
2198+
.legal-section p:last-child {
2199+
margin-bottom: 0;
2200+
}
2201+
21282202
.statusbar-zoom-controls {
21292203
display: inline-flex;
21302204
gap: 4px;

‎src/components/LegalModal.tsx‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { useEffect } from 'react'
2+
import { X } from 'lucide-react'
3+
import { LEGAL_DOCUMENTS, type LegalDocumentId } from '../content/legal'
4+
import Portal from './Portal'
5+
6+
export default function LegalModal({
7+
documentId,
8+
onClose,
9+
}: {
10+
documentId: LegalDocumentId
11+
onClose: () => void
12+
}) {
13+
const doc = LEGAL_DOCUMENTS[documentId]
14+
15+
useEffect(() => {
16+
const onKeyDown = (e: KeyboardEvent) => {
17+
if (e.key === 'Escape') onClose()
18+
}
19+
window.addEventListener('keydown', onKeyDown)
20+
return () => window.removeEventListener('keydown', onKeyDown)
21+
}, [onClose])
22+
23+
return (
24+
<Portal>
25+
<div className="modal-backdrop" onClick={onClose}>
26+
<div
27+
className="modal modal-legal"
28+
role="dialog"
29+
aria-modal="true"
30+
aria-labelledby="legal-modal-title"
31+
onClick={(e) => e.stopPropagation()}
32+
>
33+
<div className="modal-header">
34+
<h2 id="legal-modal-title">{doc.title}</h2>
35+
<button
36+
type="button"
37+
className="modal-close"
38+
aria-label="Close"
39+
onClick={onClose}
40+
>
41+
<X size={16} aria-hidden />
42+
</button>
43+
</div>
44+
<div className="modal-body legal-body">
45+
<p className="legal-updated">Last updated: {doc.updated}</p>
46+
{doc.sections.map((section) => (
47+
<section key={section.heading} className="legal-section">
48+
<h3>{section.heading}</h3>
49+
{section.paragraphs.map((paragraph, index) => (
50+
<p key={index}>{paragraph}</p>
51+
))}
52+
</section>
53+
))}
54+
</div>
55+
</div>
56+
</div>
57+
</Portal>
58+
)
59+
}

‎src/components/StatusBar.tsx‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import { useState } from 'react'
12
import { Minus, Plus } from 'lucide-react'
23
import { getToolLabel } from '../config/tools'
4+
import type { LegalDocumentId } from '../content/legal'
35
import type { LayoutMode } from '../hooks/useLayoutMode'
46
import { useAppStore } from '../store'
57
import CanvasRotationControl from './CanvasRotationControl'
8+
import LegalModal from './LegalModal'
69

710
const MIN_SCALE = 0.05
811
const MAX_SCALE = 8
@@ -17,10 +20,13 @@ export default function StatusBar({
1720
const viewport = useAppStore((s) => s.viewport)
1821
const setViewport = useAppStore((s) => s.setViewport)
1922
const pointer = useAppStore((s) => s.pointer)
23+
const [legalDoc, setLegalDoc] = useState<LegalDocumentId | null>(null)
2024

2125
const showCoords = layoutMode === 'desktop'
2226
const showZoomButtons = layoutMode === 'touch'
2327
const showRotation = layoutMode !== 'touch'
28+
const showLegalLinks = layoutMode !== 'touch'
29+
const showCredit = layoutMode === 'desktop'
2430

2531
const zoomBy = (direction: 1 | -1) => {
2632
const oldScale = viewport.scale
@@ -68,6 +74,45 @@ export default function StatusBar({
6874
</span>
6975
)}
7076
<span className="statusbar-tool">Tool: {getToolLabel(activeTool)}</span>
77+
{showLegalLinks && (
78+
<nav className="statusbar-legal" aria-label="Legal">
79+
<button
80+
type="button"
81+
className="statusbar-legal-link"
82+
onClick={() => setLegalDoc('privacy')}
83+
>
84+
Privacy
85+
</button>
86+
<span className="statusbar-legal-sep" aria-hidden>
87+
·
88+
</span>
89+
<button
90+
type="button"
91+
className="statusbar-legal-link"
92+
onClick={() => setLegalDoc('terms')}
93+
>
94+
Terms
95+
</button>
96+
{showCredit && (
97+
<>
98+
<span className="statusbar-legal-sep" aria-hidden>
99+
·
100+
</span>
101+
<a
102+
className="statusbar-legal-link statusbar-credit"
103+
href="https://anthonyriley.org"
104+
target="_blank"
105+
rel="noopener noreferrer"
106+
>
107+
by Anthony Riley ❤️
108+
</a>
109+
</>
110+
)}
111+
</nav>
112+
)}
113+
{legalDoc && (
114+
<LegalModal documentId={legalDoc} onClose={() => setLegalDoc(null)} />
115+
)}
71116
</footer>
72117
)
73118
}

‎src/components/TopBarMenu.tsx‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react'
22
import {
33
Download,
44
FilePlus,
5+
FileText,
56
FolderOpen,
67
HardDriveDownload,
78
Image,
@@ -10,9 +11,12 @@ import {
1011
RefreshCw,
1112
Route,
1213
Save,
14+
Shield,
1315
} from 'lucide-react'
16+
import type { LegalDocumentId } from '../content/legal'
1417
import type { LayoutMode } from '../hooks/useLayoutMode'
1518
import BottomSheet from './BottomSheet'
19+
import LegalModal from './LegalModal'
1620

1721
export interface TopBarMenuAction {
1822
id: string
@@ -32,10 +36,41 @@ export default function TopBarMenu({
3236
actions: TopBarMenuAction[]
3337
}) {
3438
const [open, setOpen] = useState(false)
39+
const [legalDoc, setLegalDoc] = useState<LegalDocumentId | null>(null)
3540
const dropdownRef = useRef<HTMLDivElement>(null)
3641

3742
const visibleActions = actions.filter((a) => !a.hidden)
3843

44+
const openLegal = (id: LegalDocumentId) => {
45+
setOpen(false)
46+
setLegalDoc(id)
47+
}
48+
49+
const legalMenuItems = (
50+
<ul className="topbar-menu-list">
51+
<li>
52+
<button
53+
type="button"
54+
className="topbar-menu-item"
55+
onClick={() => openLegal('privacy')}
56+
>
57+
<Shield size={16} aria-hidden />
58+
<span>Privacy Policy</span>
59+
</button>
60+
</li>
61+
<li>
62+
<button
63+
type="button"
64+
className="topbar-menu-item"
65+
onClick={() => openLegal('terms')}
66+
>
67+
<FileText size={16} aria-hidden />
68+
<span>Terms of Service</span>
69+
</button>
70+
</li>
71+
</ul>
72+
)
73+
3974
useEffect(() => {
4075
if (!open || layoutMode !== 'compact') return
4176
const onPointerDown = (e: PointerEvent) => {
@@ -90,9 +125,16 @@ export default function TopBarMenu({
90125
<h3>File</h3>
91126
{menuItems}
92127
</div>
128+
<div className="topbar-menu-section">
129+
<h3>Legal</h3>
130+
{legalMenuItems}
131+
</div>
93132
</div>
94133
</BottomSheet>
95134
)}
135+
{legalDoc && (
136+
<LegalModal documentId={legalDoc} onClose={() => setLegalDoc(null)} />
137+
)}
96138
</>
97139
)
98140
}

0 commit comments

Comments
 (0)