Skip to content

Commit 0910b2d

Browse files
authored
doc: Add React Paris banner (#931)
* ref: Move banners * doc: Add React Paris logo * doc: Add React Paris banner
1 parent 41f48e2 commit 0910b2d

File tree

10 files changed

+295
-50
lines changed

10 files changed

+295
-50
lines changed

packages/docs/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"@headlessui/react": "2.2.0",
2525
"@headlessui/tailwindcss": "^0.2.1",
2626
"@icons-pack/react-simple-icons": "^11.2.0",
27+
"@number-flow/react": "^0.5.5",
2728
"@radix-ui/react-checkbox": "^1.1.3",
2829
"@radix-ui/react-label": "^2.1.1",
2930
"@radix-ui/react-select": "^2.1.5",

packages/docs/src/app/(pages)/stats/page.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Card } from '@tremor/react'
2-
import Image from 'next/image'
32
import { SearchParams } from 'nuqs/server'
43
import { Suspense } from 'react'
54
import { NPMDownloads, NPMStats } from './_components/downloads'
@@ -33,7 +32,7 @@ export default async function StatsPage({ searchParams }: StatsPageProps) {
3332
<StarHistoryGraph />
3433
</Suspense>
3534
<Card className="flex flex-col gap-2 p-2 dark:bg-background">
36-
<Image
35+
<img
3736
width={814}
3837
height={318}
3938
alt="Project analytics and stats"

packages/docs/src/app/banners.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { Banner } from 'fumadocs-ui/components/banner'
2+
import Link from 'next/link'
3+
import { Suspense } from 'react'
4+
import { Countdown } from '../components/countdown'
5+
import { ReactParisLogo } from '../components/react-paris'
6+
7+
export function NuqsV2AnnouncementTopBanner() {
8+
return (
9+
<Banner
10+
variant="rainbow"
11+
className="text-md gap-4 font-semibold"
12+
id="nuqs-2-announcement"
13+
>
14+
<span aria-hidden>🎉</span>
15+
<Link
16+
href="/blog/nuqs-2"
17+
className="decoration-slice decoration-1 transition-all hover:underline hover:underline-offset-8 focus-visible:underline focus-visible:outline-none"
18+
prefetch={false}
19+
>
20+
Announcing nuqs version 2
21+
</Link>
22+
<span aria-hidden>🎉</span>
23+
</Banner>
24+
)
25+
}
26+
27+
export function NuqsV2AnnouncementSidebarBanner() {
28+
return (
29+
<div className="my-2 flex justify-center gap-2 rounded-lg border border-blue-500/40 bg-blue-100/50 py-2.5 font-semibold dark:bg-blue-700/10">
30+
<span aria-hidden>🎉</span>
31+
<Link
32+
href="/blog/nuqs-2"
33+
className="text-blue-900 hover:underline focus-visible:underline focus-visible:outline-none dark:text-blue-100"
34+
prefetch={false}
35+
>
36+
Announcing nuqs v2 !
37+
</Link>
38+
<span aria-hidden>🎉</span>
39+
</div>
40+
)
41+
}
42+
43+
export function ReactParis2025SideBanner() {
44+
return (
45+
<div className="my-2 flex flex-col items-center gap-1.5 rounded-lg border border-gray-500/40 bg-gray-100/50 px-2 py-4 dark:bg-gray-700/10">
46+
<p className="text-muted-foreground">🗣️ nuqs will be featured at</p>
47+
<div className="flex gap-2">
48+
<ReactParisLogo className="h-12" />
49+
<p className="mr-1">
50+
<span className="text-lg font-bold uppercase text-[#002654] dark:text-[#00acff]">
51+
React
52+
</span>{' '}
53+
<span className="text-lg uppercase text-[#cd1126] dark:text-[#fe6497]">
54+
Paris
55+
</span>{' '}
56+
<span className="text-lg">'25</span>
57+
<br />
58+
<a
59+
href="https://react.paris/#tickets"
60+
className="text-sm hover:underline"
61+
>
62+
Get your ticket now!
63+
</a>
64+
</p>
65+
</div>
66+
<Suspense>
67+
<Countdown
68+
targetDate={new Date('2025-03-20T15:00:00+01:00')}
69+
className="my-2"
70+
/>
71+
</Suspense>
72+
<p className="text-center text-xs text-muted-foreground">
73+
Use the code <code>Francois_Paris</code> for a 20% discount on your
74+
ticket.
75+
</p>
76+
</div>
77+
)
78+
}

packages/docs/src/app/docs/layout.tsx

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { source } from '@/src/app/source'
22
import { getSharedLayoutProps } from '@/src/components/shared-layout'
33
import { DocsLayout } from 'fumadocs-ui/layouts/docs'
4-
import Link from 'next/link'
54
import { Suspense, type ReactNode } from 'react'
5+
import { ReactParis2025SideBanner } from '../banners'
66

77
export default function RootDocsLayout({ children }: { children: ReactNode }) {
88
return (
@@ -11,19 +11,7 @@ export default function RootDocsLayout({ children }: { children: ReactNode }) {
1111
{...getSharedLayoutProps()}
1212
sidebar={{
1313
collapsible: false,
14-
banner: (
15-
<div className="my-2 flex justify-center gap-2 rounded-lg border border-blue-500/40 bg-blue-100/50 py-2.5 font-semibold dark:bg-blue-700/10">
16-
<span aria-hidden>🎉</span>
17-
<Link
18-
href="/blog/nuqs-2"
19-
className="text-blue-900 hover:underline focus-visible:underline focus-visible:outline-none dark:text-blue-100"
20-
prefetch={false}
21-
>
22-
Announcing nuqs v2 !
23-
</Link>
24-
<span aria-hidden>🎉</span>
25-
</div>
26-
),
14+
banner: <ReactParis2025SideBanner />,
2715
footer: (
2816
<Suspense>
2917
<SidebarFooter />

packages/docs/src/app/globals.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,9 @@
129129
fill: currentColor;
130130
}
131131
}
132+
133+
@layer components {
134+
number-flow-react::part(suffix) {
135+
@apply ml-0.5 text-sm font-medium text-muted-foreground;
136+
}
137+
}

packages/docs/src/app/layout.tsx

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import { Banner } from 'fumadocs-ui/components/banner'
21
import { RootProvider } from 'fumadocs-ui/provider'
32
import type { Metadata } from 'next'
43
import { Inter } from 'next/font/google'
5-
import Link from 'next/link'
64
import Script from 'next/script'
75
import { NuqsAdapter } from 'nuqs/adapters/next'
86
import type { ReactNode } from 'react'
97
import { ResponsiveHelper } from '../components/responsive-helpers'
108
import { cn } from '../lib/utils'
9+
import { NuqsV2AnnouncementTopBanner } from './banners'
1110
import './globals.css'
1211

1312
const inter = Inter({
@@ -40,21 +39,7 @@ export default function Layout({ children }: { children: ReactNode }) {
4039
suppressHydrationWarning
4140
>
4241
<body>
43-
<Banner
44-
variant="rainbow"
45-
className="text-md gap-4 font-semibold"
46-
id="nuqs-2-announcement"
47-
>
48-
<span aria-hidden>🎉</span>
49-
<Link
50-
href="/blog/nuqs-2"
51-
className="decoration-slice decoration-1 transition-all hover:underline hover:underline-offset-8 focus-visible:underline focus-visible:outline-none"
52-
prefetch={false}
53-
>
54-
Announcing nuqs version 2
55-
</Link>
56-
<span aria-hidden>🎉</span>
57-
</Banner>
42+
<NuqsV2AnnouncementTopBanner />
5843
<RootProvider>
5944
<NuqsAdapter>{children}</NuqsAdapter>
6045
</RootProvider>

packages/docs/src/app/playground/layout.tsx

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { getSharedLayoutProps } from '@/src/components/shared-layout'
22
import { DocsLayout } from 'fumadocs-ui/layouts/docs'
33
import { DocsBody, DocsPage } from 'fumadocs-ui/page'
4-
import Link from 'next/link'
54
import React, { Suspense } from 'react'
5+
import { ReactParis2025SideBanner } from '../banners'
66
import { getPlaygroundTree } from './(demos)/demos'
77
import { DebugControl } from './debug-control'
88

@@ -25,19 +25,7 @@ export default function PlaygroundLayout({
2525
{...getSharedLayoutProps()}
2626
sidebar={{
2727
collapsible: false,
28-
banner: (
29-
<div className="my-2 flex justify-center gap-2 rounded-lg border border-blue-500/40 bg-blue-100/50 py-2.5 font-semibold dark:bg-blue-700/10">
30-
<span aria-hidden>🎉</span>
31-
<Link
32-
href="/blog/nuqs-2"
33-
className="text-blue-900 hover:underline focus-visible:underline focus-visible:outline-none dark:text-blue-100"
34-
prefetch={false}
35-
>
36-
Announcing nuqs v2 !
37-
</Link>
38-
<span aria-hidden>🎉</span>
39-
</div>
40-
),
28+
banner: <ReactParis2025SideBanner />,
4129
footer: (
4230
<Suspense fallback={<DebugControlsSkeleton />}>
4331
<DebugControl />
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
'use client'
2+
3+
import NumberFlow, { NumberFlowGroup } from '@number-flow/react'
4+
import { ComponentProps, ReactNode, useEffect, useState } from 'react'
5+
import { cn } from '../lib/utils'
6+
7+
type CountdownProps = ComponentProps<'div'> & {
8+
targetDate: Date
9+
expiredMessage?: ReactNode
10+
}
11+
12+
export function Countdown({
13+
targetDate,
14+
expiredMessage = null,
15+
className,
16+
...props
17+
}: CountdownProps) {
18+
const remaining = targetDate.getTime() - Date.now()
19+
const [days, setDays] = useState(() =>
20+
Math.floor(remaining / 1000 / 60 / 60 / 24)
21+
)
22+
const [hours, setHours] = useState(
23+
() => Math.floor(remaining / 1000 / 60 / 60) % 24
24+
)
25+
const [minutes, setMinutes] = useState(
26+
() => Math.floor(remaining / 1000 / 60) % 60
27+
)
28+
const [seconds, setSeconds] = useState(() =>
29+
Math.floor((remaining / 1000) % 60)
30+
)
31+
32+
useEffect(() => {
33+
const timer = setInterval(() => {
34+
const remaining = targetDate.getTime() - Date.now()
35+
setDays(Math.floor(remaining / 1000 / 60 / 60 / 24))
36+
setHours(Math.floor(remaining / 1000 / 60 / 60) % 24)
37+
setMinutes(Math.floor(remaining / 1000 / 60) % 60)
38+
setSeconds(Math.floor((remaining / 1000) % 60))
39+
if (remaining <= 0) {
40+
clearInterval(timer)
41+
}
42+
}, 500)
43+
44+
return () => clearInterval(timer)
45+
}, [targetDate])
46+
47+
if (days <= 0 && hours <= 0 && minutes <= 0 && seconds <= 0) {
48+
return expiredMessage
49+
}
50+
51+
return (
52+
<div
53+
className={cn(
54+
'flex items-baseline justify-center font-bold',
55+
days > 0 ? 'gap-1 text-xl' : 'text-3xl',
56+
className
57+
)}
58+
title={targetDate.toISOString()}
59+
{...props}
60+
>
61+
<NumberFlowGroup>
62+
{days > 0 && (
63+
<NumberFlow
64+
trend={-1}
65+
value={days}
66+
className="&::part(suffix):text-gray-400 tabular-nums"
67+
suffix="d"
68+
/>
69+
)}
70+
<NumberFlow
71+
trend={-1}
72+
value={hours}
73+
className={'tabular-nums'}
74+
suffix={days > 0 ? 'h' : undefined}
75+
digits={{ 1: { max: 2 } }}
76+
format={{ minimumIntegerDigits: 2 }}
77+
/>
78+
<NumberFlow
79+
trend={-1}
80+
value={minutes}
81+
className="tabular-nums"
82+
prefix={days > 0 ? undefined : ':'}
83+
suffix={days > 0 ? 'm' : undefined}
84+
digits={{ 1: { max: 5 } }}
85+
format={{ minimumIntegerDigits: 2 }}
86+
/>
87+
<NumberFlow
88+
trend={-1}
89+
value={seconds}
90+
className="tabular-nums"
91+
prefix={days > 0 ? undefined : ':'}
92+
suffix={days > 0 ? 's' : undefined}
93+
digits={{ 1: { max: 5 } }}
94+
format={{ minimumIntegerDigits: 2 }}
95+
/>
96+
</NumberFlowGroup>
97+
</div>
98+
)
99+
}

0 commit comments

Comments
 (0)