Skip to content

Commit ea34c39

Browse files
JohnMcLearclaude
andauthored
ui: fix overview-bar layout, wire About link, add install one-liner (#365)
* fix: align @fortawesome/* and react/react-dom versions so the build compiles Two transitive version conflicts were breaking the Deploy etherpad docs to GitHub Pages workflow: 1. \`@fortawesome/free-brands-svg-icons\` was pinned to \`^7.2.0\` while the other \`@fortawesome/*\` deps were \`^7.0.0\`. pnpm resolved two copies of \`@fortawesome/fontawesome-common-types\` (7.0.0 and 7.2.0), and the TypeScript build failed with \`Type 'IconDefinition' is not assignable to type 'IconProp'. ... Type '"fagt"' is not assignable to type 'IconPrefix'\`. 2. \`react\` was \`^19.2.4\` and \`react-dom\` was \`^19.2.0\`, so pnpm resolved them to different minors (19.2.4 and 19.2.0) and Next bailed out with "Incompatible React versions: The react and react-dom packages must have the exact same version". Bump all four \`@fortawesome/*\` packages to \`^7.2.0\` and both \`react\` / \`react-dom\` to \`^19.2.5\` so pnpm resolves a single version of each. \`pnpm run build\` now completes locally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ui: fix overview-bar layout + wire About link + add install one-liner Three related UI fixes on the home page and header. 1. \`.overview-bar\` items were single flex rows with icon + main text + subtitle all as siblings, so "290 Plugins extend without forking" (and the other three items) rendered with the subtitle inline next to the primary label instead of stacked underneath. Wrap the main text and subtitle in a \`flex-col\` so the subtitle drops below the label the way the rest of the design assumes. 2. The header's \`About\` link scrolled to a no-op \`<a id="about">\` marker on the home page when it should have routed to the dedicated \`/about\` manifesto page. Use Next's \`<Link href="/about">\` directly. Also route the logo back to \`/\` via \`<Link>\` so in-app navigation doesn't trigger a full page reload. 3. Replace the plain "Self-host in 5 minutes" link-to-README button with a new \`<InstallOneLiner>\` component: a tabbed Linux / macOS / Windows switcher that shows the Docker one-liner, a Copy button, and a link to the full install docs. The user can paste the command straight into a terminal without leaving the landing page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent bf78298 commit ea34c39

3 files changed

Lines changed: 116 additions & 14 deletions

File tree

src/components/InstallOneLiner.tsx

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
'use client'
2+
import {useState} from "react";
3+
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
4+
import {faLinux, faApple, faWindows} from "@fortawesome/free-brands-svg-icons";
5+
import {faCopy, faCheck} from "@fortawesome/free-solid-svg-icons";
6+
7+
type OS = 'linux' | 'mac' | 'windows';
8+
9+
const ONE_LINERS: Record<OS, {label: string; command: string; icon: typeof faLinux}> = {
10+
linux: {
11+
label: 'Linux',
12+
icon: faLinux,
13+
command: 'docker run -d --name etherpad -p 9001:9001 etherpad/etherpad',
14+
},
15+
mac: {
16+
label: 'macOS',
17+
icon: faApple,
18+
command: 'docker run -d --name etherpad -p 9001:9001 etherpad/etherpad',
19+
},
20+
windows: {
21+
label: 'Windows',
22+
icon: faWindows,
23+
command: 'docker run -d --name etherpad -p 9001:9001 etherpad/etherpad',
24+
},
25+
};
26+
27+
export const InstallOneLiner = () => {
28+
const [os, setOs] = useState<OS>('linux');
29+
const [copied, setCopied] = useState(false);
30+
31+
const copy = async () => {
32+
try {
33+
await navigator.clipboard.writeText(ONE_LINERS[os].command);
34+
setCopied(true);
35+
setTimeout(() => setCopied(false), 1500);
36+
} catch {
37+
// Clipboard unavailable — the command is still selectable in the <code>.
38+
}
39+
};
40+
41+
return <div className="mt-4 rounded border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-700 overflow-hidden max-w-xl">
42+
<div className="flex text-sm">
43+
{(Object.keys(ONE_LINERS) as OS[]).map((key) => {
44+
const active = os === key;
45+
return <button
46+
key={key}
47+
type="button"
48+
onClick={() => setOs(key)}
49+
className={`flex-1 px-3 py-2 border-b-2 transition ${
50+
active
51+
? 'border-primary text-primary bg-white dark:bg-gray-800'
52+
: 'border-transparent text-gray-600 dark:text-gray-300 hover:bg-white/60 dark:hover:bg-gray-800/50'
53+
}`}>
54+
<FontAwesomeIcon icon={ONE_LINERS[key].icon} className="mr-2"/>
55+
{ONE_LINERS[key].label}
56+
</button>;
57+
})}
58+
</div>
59+
<div className="flex items-center justify-between gap-2 p-3">
60+
<code className="flex-1 text-sm font-mono break-all text-gray-800 dark:text-gray-100">
61+
{ONE_LINERS[os].command}
62+
</code>
63+
<button
64+
type="button"
65+
onClick={copy}
66+
className="shrink-0 px-2 py-1 text-sm text-primary hover:bg-primary hover:text-white rounded"
67+
aria-label="Copy install command">
68+
<FontAwesomeIcon icon={copied ? faCheck : faCopy} className="mr-1"/>
69+
{copied ? 'Copied' : 'Copy'}
70+
</button>
71+
</div>
72+
<p className="text-xs text-gray-500 dark:text-gray-400 px-3 pb-3">
73+
Needs Docker. Then open <code>http://localhost:9001</code>.{' '}
74+
<a
75+
href="https://github.com/ether/etherpad-lite#installation"
76+
target="_blank"
77+
rel="noreferrer"
78+
className="underline">
79+
Other install options
80+
</a>.
81+
</p>
82+
</div>;
83+
};

src/pagesToDisplay/Header.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,37 @@ import {faBars} from "@fortawesome/free-solid-svg-icons";
66
import {MobileDrawer} from "../components/MobileDrawer.tsx";
77
import {ThemeToggler} from "../components/ThemeToggler.tsx";
88
import {useRouter} from "next/navigation";
9+
import Link from "next/link";
910
import Image from "next/image";
1011

1112
export const Header = () => {
1213
const [mobileDrawerOpen, setMobileDrawerOpen] = useState<boolean>(false)
1314
const navigate = useRouter()
1415

16+
// Scroll to an anchor on the home page. If the user is on a different
17+
// route (e.g. /about), push back to the home page with the hash so the
18+
// scroll-point element actually exists before we try to find it.
1519
const navigateToElement = (elementId: string)=>{
16-
document.getElementById(elementId)?.scrollIntoView({block: "start", inline: "nearest"})
17-
navigate.push('/#'+elementId)
20+
const el = document.getElementById(elementId)
21+
if (el) {
22+
el.scrollIntoView({block: "start", inline: "nearest"})
23+
navigate.push('/#'+elementId)
24+
} else {
25+
navigate.push('/#'+elementId)
26+
}
1827
}
1928

2029
return <><div id="header" className="text-white border-b-[1pt] border-solid border-[#efefef] p-4 w-full bg-white dark:bg-secondary-dark">
2130
<div className="wrap flex items-center justify-self-center">
22-
<a href="#">
31+
<Link href="/">
2332
<Suspense>
2433
<Image className="logo h-8 image-logo" src={brandSvg} alt="etherpad logo"/>
2534
</Suspense>
26-
</a>
35+
</Link>
2736

2837
<div id="nav">
2938
<ul>
30-
<li><a className="text-[#555] dark:text-white" onClick={()=>navigateToElement('about')} title="about">About</a></li>
39+
<li><Link className="text-[#555] dark:text-white" href="/about" title="about">About</Link></li>
3140
<li><a className="text-[#555] dark:text-white" onClick={()=>navigateToElement('download')} title="download">Download</a></li>
3241
<li><a className="text-[#555] dark:text-white" onClick={()=>navigateToElement('contribute')} title="contribute">Contribute</a></li>
3342
<li><a className="text-[#555] dark:text-white" onClick={()=>navigateToElement("links")} title="links">Links</a></li>

src/pagesToDisplay/MainHeadline.tsx

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
33
import {faCogs, faLanguage, faServer, faUsers} from "@fortawesome/free-solid-svg-icons";
44
import {Suspense} from "react";
55
import Link from "next/link";
6+
import {InstallOneLiner} from "../components/InstallOneLiner.tsx";
67
export const MainHeadline = () => {
78
return <div className="content primary showcase">
89
<div className="wrap">
@@ -15,8 +16,9 @@ export const MainHeadline = () => {
1516
<div className="flex flex-wrap gap-3 mb-6">
1617
<Link href="/about" className="px-4 py-2 bg-primary text-white rounded hover:opacity-90">Read the manifesto &rarr;</Link>
1718
<Link href="/why-etherpad" className="px-4 py-2 border border-primary text-primary rounded hover:bg-primary hover:text-white">Why Etherpad &rarr;</Link>
18-
<a href="https://github.com/ether/etherpad-lite#installation" target="_blank" className="px-4 py-2 border border-primary text-primary rounded hover:bg-primary hover:text-white">Self-host in 5 minutes &rarr;</a>
1919
</div>
20+
21+
<InstallOneLiner/>
2022
</div>
2123

2224
<div className="demo justify-center flex">
@@ -32,23 +34,31 @@ export const MainHeadline = () => {
3234
<div className="overview-bar dark:bg-gray-600 dark:text-white">
3335
<div className="item">
3436
<FontAwesomeIcon icon={faCogs} className="mr-2"/>
35-
<Link href="/plugins" target="_blank" className="underline">290 Plugins</Link>
36-
<span className="block text-sm opacity-75">extend without forking</span>
37+
<div className="flex flex-col leading-tight">
38+
<Link href="/plugins" target="_blank" className="underline">290 Plugins</Link>
39+
<span className="text-sm opacity-75">extend without forking</span>
40+
</div>
3741
</div>
3842
<div className="item">
3943
<FontAwesomeIcon icon={faLanguage} className="mr-2"/>
40-
105 Languages
41-
<span className="block text-sm opacity-75">translated by a global community</span>
44+
<div className="flex flex-col leading-tight">
45+
<span>105 Languages</span>
46+
<span className="text-sm opacity-75">translated by a global community</span>
47+
</div>
4248
</div>
4349
<div className="item">
4450
<FontAwesomeIcon icon={faServer} className="mr-2"/>
45-
Thousands of Instances
46-
<span className="block text-sm opacity-75">Raspberry Pis to data centres</span>
51+
<div className="flex flex-col leading-tight">
52+
<span>Thousands of Instances</span>
53+
<span className="text-sm opacity-75">Raspberry Pis to data centres</span>
54+
</div>
4755
</div>
4856
<div className="item">
4957
<FontAwesomeIcon icon={faUsers} className="mr-2"/>
50-
Millions of Users
51-
<span className="block text-sm opacity-75">Wikimedia, governments, schools</span>
58+
<div className="flex flex-col leading-tight">
59+
<span>Millions of Users</span>
60+
<span className="text-sm opacity-75">Wikimedia, governments, schools</span>
61+
</div>
5262
</div>
5363
</div>
5464
</div>

0 commit comments

Comments
 (0)