+ 🎉
+
+ Announcing nuqs version 2
+
+ 🎉
+
+ )
+}
+
+export function NuqsV2AnnouncementSidebarBanner() {
+ return (
+
+ 🎉
+
+ Announcing nuqs v2 !
+
+ 🎉
+
+ )
+}
+
+export function ReactParis2025SideBanner() {
+ return (
+
+
🗣️ nuqs will be featured at
+
+
+
+
+
+ Use the code Francois_Paris
for a 20% discount on your
+ ticket.
+
+
+ )
+}
diff --git a/packages/docs/src/app/docs/layout.tsx b/packages/docs/src/app/docs/layout.tsx
index 77be63d04..66650f723 100644
--- a/packages/docs/src/app/docs/layout.tsx
+++ b/packages/docs/src/app/docs/layout.tsx
@@ -1,8 +1,8 @@
import { source } from '@/src/app/source'
import { getSharedLayoutProps } from '@/src/components/shared-layout'
import { DocsLayout } from 'fumadocs-ui/layouts/docs'
-import Link from 'next/link'
import { Suspense, type ReactNode } from 'react'
+import { ReactParis2025SideBanner } from '../banners'
export default function RootDocsLayout({ children }: { children: ReactNode }) {
return (
@@ -11,19 +11,7 @@ export default function RootDocsLayout({ children }: { children: ReactNode }) {
{...getSharedLayoutProps()}
sidebar={{
collapsible: false,
- banner: (
-
- 🎉
-
- Announcing nuqs v2 !
-
- 🎉
-
- ),
+ banner: ,
footer: (
diff --git a/packages/docs/src/app/globals.css b/packages/docs/src/app/globals.css
index 6d5d4e6ff..a1bce33a3 100644
--- a/packages/docs/src/app/globals.css
+++ b/packages/docs/src/app/globals.css
@@ -129,3 +129,9 @@
fill: currentColor;
}
}
+
+@layer components {
+ number-flow-react::part(suffix) {
+ @apply ml-0.5 text-sm font-medium text-muted-foreground;
+ }
+}
diff --git a/packages/docs/src/app/layout.tsx b/packages/docs/src/app/layout.tsx
index ae3b2a627..4265e2c6d 100644
--- a/packages/docs/src/app/layout.tsx
+++ b/packages/docs/src/app/layout.tsx
@@ -1,13 +1,12 @@
-import { Banner } from 'fumadocs-ui/components/banner'
import { RootProvider } from 'fumadocs-ui/provider'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
-import Link from 'next/link'
import Script from 'next/script'
import { NuqsAdapter } from 'nuqs/adapters/next'
import type { ReactNode } from 'react'
import { ResponsiveHelper } from '../components/responsive-helpers'
import { cn } from '../lib/utils'
+import { NuqsV2AnnouncementTopBanner } from './banners'
import './globals.css'
const inter = Inter({
@@ -40,21 +39,7 @@ export default function Layout({ children }: { children: ReactNode }) {
suppressHydrationWarning
>
-
- 🎉
-
- Announcing nuqs version 2
-
- 🎉
-
+
{children}
diff --git a/packages/docs/src/app/playground/layout.tsx b/packages/docs/src/app/playground/layout.tsx
index 9fe0fcbdf..c259524d7 100644
--- a/packages/docs/src/app/playground/layout.tsx
+++ b/packages/docs/src/app/playground/layout.tsx
@@ -1,8 +1,8 @@
import { getSharedLayoutProps } from '@/src/components/shared-layout'
import { DocsLayout } from 'fumadocs-ui/layouts/docs'
import { DocsBody, DocsPage } from 'fumadocs-ui/page'
-import Link from 'next/link'
import React, { Suspense } from 'react'
+import { ReactParis2025SideBanner } from '../banners'
import { getPlaygroundTree } from './(demos)/demos'
import { DebugControl } from './debug-control'
@@ -25,19 +25,7 @@ export default function PlaygroundLayout({
{...getSharedLayoutProps()}
sidebar={{
collapsible: false,
- banner: (
-
- 🎉
-
- Announcing nuqs v2 !
-
- 🎉
-
- ),
+ banner: ,
footer: (
}>
diff --git a/packages/docs/src/components/countdown.tsx b/packages/docs/src/components/countdown.tsx
new file mode 100644
index 000000000..d82e255aa
--- /dev/null
+++ b/packages/docs/src/components/countdown.tsx
@@ -0,0 +1,99 @@
+'use client'
+
+import NumberFlow, { NumberFlowGroup } from '@number-flow/react'
+import { ComponentProps, ReactNode, useEffect, useState } from 'react'
+import { cn } from '../lib/utils'
+
+type CountdownProps = ComponentProps<'div'> & {
+ targetDate: Date
+ expiredMessage?: ReactNode
+}
+
+export function Countdown({
+ targetDate,
+ expiredMessage = null,
+ className,
+ ...props
+}: CountdownProps) {
+ const remaining = targetDate.getTime() - Date.now()
+ const [days, setDays] = useState(() =>
+ Math.floor(remaining / 1000 / 60 / 60 / 24)
+ )
+ const [hours, setHours] = useState(
+ () => Math.floor(remaining / 1000 / 60 / 60) % 24
+ )
+ const [minutes, setMinutes] = useState(
+ () => Math.floor(remaining / 1000 / 60) % 60
+ )
+ const [seconds, setSeconds] = useState(() =>
+ Math.floor((remaining / 1000) % 60)
+ )
+
+ useEffect(() => {
+ const timer = setInterval(() => {
+ const remaining = targetDate.getTime() - Date.now()
+ setDays(Math.floor(remaining / 1000 / 60 / 60 / 24))
+ setHours(Math.floor(remaining / 1000 / 60 / 60) % 24)
+ setMinutes(Math.floor(remaining / 1000 / 60) % 60)
+ setSeconds(Math.floor((remaining / 1000) % 60))
+ if (remaining <= 0) {
+ clearInterval(timer)
+ }
+ }, 500)
+
+ return () => clearInterval(timer)
+ }, [targetDate])
+
+ if (days <= 0 && hours <= 0 && minutes <= 0 && seconds <= 0) {
+ return expiredMessage
+ }
+
+ return (
+ 0 ? 'gap-1 text-xl' : 'text-3xl',
+ className
+ )}
+ title={targetDate.toISOString()}
+ {...props}
+ >
+
+ {days > 0 && (
+
+ )}
+ 0 ? 'h' : undefined}
+ digits={{ 1: { max: 2 } }}
+ format={{ minimumIntegerDigits: 2 }}
+ />
+ 0 ? undefined : ':'}
+ suffix={days > 0 ? 'm' : undefined}
+ digits={{ 1: { max: 5 } }}
+ format={{ minimumIntegerDigits: 2 }}
+ />
+ 0 ? undefined : ':'}
+ suffix={days > 0 ? 's' : undefined}
+ digits={{ 1: { max: 5 } }}
+ format={{ minimumIntegerDigits: 2 }}
+ />
+
+
+ )
+}
diff --git a/packages/docs/src/components/react-paris.tsx b/packages/docs/src/components/react-paris.tsx
new file mode 100644
index 000000000..7fd0ccd53
--- /dev/null
+++ b/packages/docs/src/components/react-paris.tsx
@@ -0,0 +1,45 @@
+import { ComponentProps } from 'react'
+
+export function ReactParisLogo(props: ComponentProps<'svg'>) {
+ return (
+
+ )
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4ce20bb7c..d48ea7eea 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -72,6 +72,9 @@ importers:
'@icons-pack/react-simple-icons':
specifier: ^11.2.0
version: 11.2.0(react@19.0.0)
+ '@number-flow/react':
+ specifier: ^0.5.5
+ version: 0.5.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@radix-ui/react-checkbox':
specifier: ^1.1.3
version: 1.1.3(@types/react-dom@19.0.0)(@types/react@19.0.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@@ -554,7 +557,7 @@ importers:
version: 26.0.0
next:
specifier: 15.1.5
- version: 15.1.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-201e55d-20241215)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ version: 15.1.5(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react:
specifier: catalog:react19
version: 19.0.0
@@ -1614,6 +1617,12 @@ packages:
resolution: {integrity: sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+ '@number-flow/react@0.5.5':
+ resolution: {integrity: sha512-Zdju5n0osxrb+7jbcpUJ9L2VJ2+9ptwjz5+A+2wq9Q32hs3PW/noPJjHtLTrtGINM9mEw76DcDg0ac/dx6j1aA==}
+ peerDependencies:
+ react: ^18 || ^19
+ react-dom: ^18 || ^19
+
'@octokit/auth-token@5.1.2':
resolution: {integrity: sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==}
engines: {node: '>= 18'}
@@ -4213,6 +4222,9 @@ packages:
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
engines: {node: '>=8.0.0'}
+ esm-env@1.2.2:
+ resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==}
+
esprima@4.0.1:
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
engines: {node: '>=4'}
@@ -5997,6 +6009,9 @@ packages:
- which
- write-file-atomic
+ number-flow@0.5.3:
+ resolution: {integrity: sha512-iLKyssImNWQmJ41rza9K7P5lHRZTyishi/9FarWPLQHYY2Ydtl6eiXINEjZ1fa8dHeY0O7+YOD+Py3ZsJddYkg==}
+
nwsapi@2.2.16:
resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==}
@@ -9049,6 +9064,13 @@ snapshots:
dependencies:
which: 3.0.1
+ '@number-flow/react@0.5.5(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ esm-env: 1.2.2
+ number-flow: 0.5.3
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+
'@octokit/auth-token@5.1.2': {}
'@octokit/core@6.1.3':
@@ -12124,6 +12146,8 @@ snapshots:
esrecurse: 4.3.0
estraverse: 4.3.0
+ esm-env@1.2.2: {}
+
esprima@4.0.1: {}
esrecurse@4.3.0:
@@ -14294,6 +14318,32 @@ snapshots:
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
+ next@15.1.5(@babel/core@7.26.7)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ dependencies:
+ '@next/env': 15.1.5
+ '@swc/counter': 0.1.3
+ '@swc/helpers': 0.5.15
+ busboy: 1.6.0
+ caniuse-lite: 1.0.30001697
+ postcss: 8.4.31
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ styled-jsx: 5.1.6(@babel/core@7.26.7)(react@19.0.0)
+ optionalDependencies:
+ '@next/swc-darwin-arm64': 15.1.5
+ '@next/swc-darwin-x64': 15.1.5
+ '@next/swc-linux-arm64-gnu': 15.1.5
+ '@next/swc-linux-arm64-musl': 15.1.5
+ '@next/swc-linux-x64-gnu': 15.1.5
+ '@next/swc-linux-x64-musl': 15.1.5
+ '@next/swc-win32-arm64-msvc': 15.1.5
+ '@next/swc-win32-x64-msvc': 15.1.5
+ '@opentelemetry/api': 1.9.0
+ sharp: 0.33.5
+ transitivePeerDependencies:
+ - '@babel/core'
+ - babel-plugin-macros
+
next@15.1.5(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-201e55d-20241215)(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
'@next/env': 15.1.5
@@ -14304,7 +14354,7 @@ snapshots:
postcss: 8.4.31
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
- styled-jsx: 5.1.6(react@19.0.0)
+ styled-jsx: 5.1.6(@babel/core@7.26.7)(react@19.0.0)
optionalDependencies:
'@next/swc-darwin-arm64': 15.1.5
'@next/swc-darwin-x64': 15.1.5
@@ -14392,6 +14442,10 @@ snapshots:
npm@10.9.2: {}
+ number-flow@0.5.3:
+ dependencies:
+ esm-env: 1.2.2
+
nwsapi@2.2.16: {}
object-assign@4.1.1: {}
@@ -15734,10 +15788,12 @@ snapshots:
dependencies:
inline-style-parser: 0.2.4
- styled-jsx@5.1.6(react@19.0.0):
+ styled-jsx@5.1.6(@babel/core@7.26.7)(react@19.0.0):
dependencies:
client-only: 0.0.1
react: 19.0.0
+ optionalDependencies:
+ '@babel/core': 7.26.7
sucrase@3.35.0:
dependencies: