Skip to content

Commit 598cb75

Browse files
committed
doc: Add React Paris banner
1 parent 8daacbb commit 598cb75

File tree

8 files changed

+210
-9
lines changed

8 files changed

+210
-9
lines changed

packages/docs/package.json

+1
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

+1-2
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

+40
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { Banner } from 'fumadocs-ui/components/banner'
22
import Link from 'next/link'
3+
import { Suspense } from 'react'
4+
import { Countdown } from '../components/countdown'
5+
import { ReactParisLogo } from '../components/react-paris'
36

47
export function NuqsV2AnnouncementTopBanner() {
58
return (
@@ -36,3 +39,40 @@ export function NuqsV2AnnouncementSidebarBanner() {
3639
</div>
3740
)
3841
}
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

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { source } from '@/src/app/source'
22
import { getSharedLayoutProps } from '@/src/components/shared-layout'
33
import { DocsLayout } from 'fumadocs-ui/layouts/docs'
44
import { Suspense, type ReactNode } from 'react'
5-
import { NuqsV2AnnouncementSidebarBanner } from '../banners'
5+
import { ReactParis2025SideBanner } from '../banners'
66

77
export default function RootDocsLayout({ children }: { children: ReactNode }) {
88
return (
@@ -11,7 +11,7 @@ export default function RootDocsLayout({ children }: { children: ReactNode }) {
1111
{...getSharedLayoutProps()}
1212
sidebar={{
1313
collapsible: false,
14-
banner: <NuqsV2AnnouncementSidebarBanner />,
14+
banner: <ReactParis2025SideBanner />,
1515
footer: (
1616
<Suspense>
1717
<SidebarFooter />

packages/docs/src/app/globals.css

+6
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/playground/layout.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { getSharedLayoutProps } from '@/src/components/shared-layout'
22
import { DocsLayout } from 'fumadocs-ui/layouts/docs'
33
import { DocsBody, DocsPage } from 'fumadocs-ui/page'
44
import React, { Suspense } from 'react'
5-
import { NuqsV2AnnouncementSidebarBanner } from '../banners'
5+
import { ReactParis2025SideBanner } from '../banners'
66
import { getPlaygroundTree } from './(demos)/demos'
77
import { DebugControl } from './debug-control'
88

@@ -25,7 +25,7 @@ export default function PlaygroundLayout({
2525
{...getSharedLayoutProps()}
2626
sidebar={{
2727
collapsible: false,
28-
banner: <NuqsV2AnnouncementSidebarBanner />,
28+
banner: <ReactParis2025SideBanner />,
2929
footer: (
3030
<Suspense fallback={<DebugControlsSkeleton />}>
3131
<DebugControl />
+99
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+
}

pnpm-lock.yaml

+59-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)