Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/app/docs/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default function DocsIndexPage() {
Let’s get you <em>started.</em>
</h1>
<p>From your first board to your next big idea.</p>
<Link className="edition-button edition-button-paper" href={docHref('quickstart')}>
<Link className="edition-button" href={docHref('quickstart')}>
Start the quickstart <span aria-hidden>↗</span>
</Link>
</header>
Expand Down
16 changes: 12 additions & 4 deletions apps/web/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const communities = [
body: 'Keep the conversations that bring your people together.',
},
{
name: 'Clubs & associations',
name: 'Clubs and associations',
slug: 'clubs-and-associations',
body: 'A shared space for members, plans and club life.',
},
Expand Down Expand Up @@ -95,14 +95,19 @@ export default async function LandingPage() {
On your domain. On your terms.
</p>
<div className="edition-hero-actions">
<Link className="edition-button edition-button-paper" href={quickstartHref()}>
<Link className="edition-button" href={quickstartHref()}>
Start your forum <span aria-hidden="true">↗</span>
</Link>
<ForumLink className="edition-hero-source" href={site.demo}>
Try the demo <span aria-hidden="true">↗</span>
</ForumLink>
<ForumLink className="edition-hero-source">
See Meith in use <span aria-hidden="true">↗</span>
Join the community <span aria-hidden="true">↗</span>
</ForumLink>
</div>
<p className="edition-hero-note">Free & open source. Self-hosted. Yours to keep.</p>
<p className="edition-hero-note">
Free and open source. Self-hosted. Yours to keep. The demo runs on sample data.
</p>
</div>
</div>
</div>
Expand Down Expand Up @@ -265,6 +270,9 @@ export default async function LandingPage() {
<Link className="edition-button" href={quickstartHref()}>
Create your community <span aria-hidden="true">↗</span>
</Link>
<ForumLink className="edition-text-link" href={site.demo}>
Try the demo <span aria-hidden="true">↗</span>
</ForumLink>
<Link className="edition-text-link" href="/docs">
Read the docs <span aria-hidden="true">↗</span>
</Link>
Expand Down
10 changes: 10 additions & 0 deletions apps/web/content/docs.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,16 @@
"primary": false,
"group": "Development workflow"
},
{
"slug": "brand",
"file": "contributing/brand.md",
"section": "contributing",
"title": "Brand guidelines",
"blurb": "How Meith looks, sounds and spends its one colour.",
"generated": false,
"primary": false,
"group": "Development workflow"
},
{
"slug": "repository-checks",
"file": "contributing/repository-checks.md",
Expand Down
26 changes: 18 additions & 8 deletions apps/web/e2e/marketing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,22 @@ test('colour scheme persists across navigation and resets to the system preferen
page,
}) => {
await page.goto('/')
await page.getByRole('button', { name: 'Colour scheme: system. Switch to light' }).click()
const scheme = page.getByRole('group', { name: 'Colour scheme' })
await scheme.getByRole('button', { name: 'Light' }).click()
await expect(page.locator('html')).toHaveAttribute('data-theme', 'light')
await page.getByRole('button', { name: 'Colour scheme: light. Switch to dark' }).click()
await scheme.getByRole('button', { name: 'Dark' }).click()
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark')

await page.goto('/about')
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark')
await page.getByRole('button', { name: 'Colour scheme: dark. Switch to system' }).click()
await expect(scheme.getByRole('button', { name: 'Dark' })).toHaveAttribute('aria-pressed', 'true')
await scheme.getByRole('button', { name: 'System' }).click()
await expect(page.locator('html')).not.toHaveAttribute('data-theme')
await page.reload()
await expect(
page.getByRole('button', { name: 'Colour scheme: system. Switch to light' }),
).toBeVisible()
await expect(scheme.getByRole('button', { name: 'System' })).toHaveAttribute(
'aria-pressed',
'true',
)
})

test('mobile navigation closes on Escape, outside interaction, and link selection', async ({
Expand All @@ -70,7 +73,10 @@ test('mobile navigation closes on Escape, outside interaction, and link selectio
await expect(menu).toBeFocused()

await menu.click()
await page.getByRole('button', { name: /^Colour scheme:/ }).click()
await page
.getByRole('group', { name: 'Colour scheme' })
.getByRole('button', { name: 'System' })
.click()
await expect(navigation).toBeHidden()

await menu.click()
Expand All @@ -92,6 +98,10 @@ test('site navigation points to real destinations and exposes customisation guid
] as const) {
await expect(nav.getByRole('link', { name, exact: true })).toHaveAttribute('href', href)
}
await expect(nav.getByRole('link', { name: /^Demo/ })).toHaveAttribute(
'href',
/^https:\/\/demo\./,
)
await expect(nav.getByRole('link', { name: /^Community/ })).toHaveAttribute(
'href',
/^https:\/\/forum\./,
Expand All @@ -110,7 +120,7 @@ test('site navigation points to real destinations and exposes customisation guid
{ name: /^Developers/, slug: 'developers' },
{ name: /^Open-source projects/, slug: 'open-source' },
{ name: /^Communities/, slug: 'communities' },
{ name: /^Clubs & associations/, slug: 'clubs-and-associations' },
{ name: /^Clubs and associations/, slug: 'clubs-and-associations' },
]

for (const audience of audiences) {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/docs-search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export function DocsSearch() {
className="inline-flex items-center gap-2 rounded-[var(--radius-control)] border border-border px-2.5 py-1.5 text-micro text-fg-subtle transition-colors hover:border-border-strong hover:text-fg"
>
<span aria-hidden>⌕</span>
<span>Search</span>
<span>Search the docs</span>
<kbd className="kbd hidden sm:inline">⌘K</kbd>
</button>

Expand Down
12 changes: 10 additions & 2 deletions apps/web/src/components/forum-link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@ import type { ReactNode } from 'react'

import { site } from '../content/site'

export function ForumLink({ children, className }: { children: ReactNode; className?: string }) {
export function ForumLink({
children,
className,
href = site.forum,
}: {
children: ReactNode
className?: string
href?: string
}) {
return (
<a className={className} href={site.forum} rel="noreferrer" target="_blank">
<a className={className} href={href} rel="noreferrer" target="_blank">
{children}
<span className="sr-only"> (opens in a new tab)</span>
</a>
Expand Down
23 changes: 23 additions & 0 deletions apps/web/src/components/nav-link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client'

import Link from 'next/link'
import { usePathname } from 'next/navigation'
import type { ReactNode } from 'react'

export function NavLink({
href,
children,
}: {
readonly href: string
readonly children: ReactNode
}) {
const pathname = usePathname()
const current =
href === '/' ? pathname === '/' : pathname === href || pathname.startsWith(`${href}/`)

return (
<Link href={href} aria-current={current ? 'page' : undefined}>
{children}
</Link>
)
}
4 changes: 2 additions & 2 deletions apps/web/src/components/site-bands.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ export function ClosingBand({
</div>
<div className="marketing-actions">
<Link className="btn btn-primary" href={startHref}>
Get started <span aria-hidden>→</span>
Get started <span aria-hidden>↗</span>
</Link>
{docsHref === undefined ? (
<ForumLink className="btn btn-quiet">
Meet the community <span aria-hidden>↗</span>
</ForumLink>
) : (
<Link className="btn btn-quiet" href={docsHref}>
Explore the docs <span aria-hidden>→</span>
Explore the docs <span aria-hidden>↗</span>
</Link>
)}
</div>
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/components/site-footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { about } from '../content/about'
import { audienceIndexHref } from '../content/segments'
import { licence, licenceHref, site } from '../content/site'
import { version } from '../content/version'
import { ForumLink } from './forum-link'

export function SiteFooter() {
return (
Expand All @@ -18,6 +19,12 @@ export function SiteFooter() {
<Link href={audienceIndexHref}>Who it’s for</Link>
<Link href="/marketplace">Extensions</Link>
<Link href="/docs">Docs</Link>
<ForumLink href={site.demo}>
Demo <span aria-hidden>↗</span>
</ForumLink>
<ForumLink>
Community <span aria-hidden>↗</span>
</ForumLink>
<a href={site.repository}>
GitHub <span aria-hidden>↗</span>
</a>
Expand Down
18 changes: 14 additions & 4 deletions apps/web/src/components/site-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import Link from 'next/link'

import { about } from '../content/about'
import { audienceIndexHref } from '../content/segments'
import { site } from '../content/site'
import { quickstartHref } from '../docs/registry'
import { DocsSearch } from './docs-search'
import { ForumLink } from './forum-link'
import { Logomark } from './logomark'
import { MobileMenu } from './mobile-menu'
import { NavLink } from './nav-link'
import { ThemeToggle } from './theme-toggle'

export function SiteHeader() {
Expand All @@ -21,10 +23,13 @@ export function SiteHeader() {
</Link>

<nav aria-label="Site" className="site-navigation">
<Link href="/">Home</Link>
<Link href={about.href}>About</Link>
<Link href={audienceIndexHref}>Who it’s for</Link>
<Link href="/docs">Docs</Link>
<NavLink href="/">Home</NavLink>
<NavLink href={about.href}>About</NavLink>
<NavLink href={audienceIndexHref}>Who it’s for</NavLink>
<NavLink href="/docs">Docs</NavLink>
<ForumLink href={site.demo}>
Demo <span aria-hidden>↗</span>
</ForumLink>
<ForumLink>
Community <span aria-hidden>↗</span>
</ForumLink>
Expand Down Expand Up @@ -61,6 +66,11 @@ export function SiteHeader() {
Docs <span aria-hidden>↗</span>
</Link>
</li>
<li>
<ForumLink href={site.demo}>
Demo <span aria-hidden>↗</span>
</ForumLink>
</li>
<li>
<ForumLink>
Community <span aria-hidden>↗</span>
Expand Down
92 changes: 52 additions & 40 deletions apps/web/src/components/theme-toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { THEME_STORAGE_KEY } from '../theme-storage'

type Choice = 'light' | 'dark' | 'system'

const NEXT_CHOICE = { system: 'light', light: 'dark', dark: 'system' } as const
const CHOICES: readonly { readonly value: Choice; readonly label: string }[] = [
{ value: 'light', label: 'Light' },
{ value: 'dark', label: 'Dark' },
{ value: 'system', label: 'System' },
]

function apply(choice: Choice) {
const root = document.documentElement
Expand All @@ -19,6 +23,36 @@ function apply(choice: Choice) {
}
}

function Glyph({ choice }: { choice: Choice }) {
return (
<svg
width="15"
height="15"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
{choice === 'light' ? (
<>
<circle cx="12" cy="12" r="4" />
<path d="M12 2v2m0 16v2M2 12h2m16 0h2M4.9 4.9l1.4 1.4m11.4 11.4 1.4 1.4M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
</>
) : choice === 'dark' ? (
<path d="M20.4 14.1A8.7 8.7 0 0 1 9.9 3.6a8.8 8.8 0 1 0 10.5 10.5Z" />
) : (
<>
<rect x="3" y="4" width="18" height="13" rx="2" />
<path d="M8 21h8m-4-4v4" />
</>
)}
</svg>
)
}

export function ThemeToggle() {
const [choice, setChoice] = useState<Choice>('system')

Expand All @@ -27,45 +61,23 @@ export function ThemeToggle() {
if (stored === 'light' || stored === 'dark') setChoice(stored)
}, [])

const next = NEXT_CHOICE[choice]
const label = `Colour scheme: ${choice}. Switch to ${next}`

return (
<button
type="button"
className="site-theme-toggle"
aria-label={label}
title={label}
onClick={() => {
setChoice(next)
apply(next)
}}
>
<svg
width="19"
height="19"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
{choice === 'light' ? (
<>
<circle cx="12" cy="12" r="4" />
<path d="M12 2v2m0 16v2M2 12h2m16 0h2M4.9 4.9l1.4 1.4m11.4 11.4 1.4 1.4M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
</>
) : choice === 'dark' ? (
<path d="M20.4 14.1A8.7 8.7 0 0 1 9.9 3.6a8.8 8.8 0 1 0 10.5 10.5Z" />
) : (
<>
<rect x="3" y="4" width="18" height="13" rx="2" />
<path d="M8 21h8m-4-4v4" />
</>
)}
</svg>
</button>
<div role="group" aria-label="Colour scheme" className="site-theme-toggle">
{CHOICES.map((option) => (
<button
key={option.value}
type="button"
aria-pressed={choice === option.value}
aria-label={option.label}
title={option.label}
onClick={() => {
setChoice(option.value)
apply(option.value)
}}
>
<Glyph choice={option.value} />
</button>
))}
</div>
)
}
Loading
Loading