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
99 changes: 75 additions & 24 deletions components/docs-shell.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
"use client"

import { useEffect, useRef, type CSSProperties, type ReactNode } from "react"
import {
useEffect,
useId,
useRef,
type CSSProperties,
type ReactNode,
} from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"

Expand Down Expand Up @@ -40,30 +46,73 @@ type FolderNode = {
type RootNode = { type?: "root"; name: ReactNode; children: TreeNode[] }
type TreeNode = PageNode | SeparatorNode | FolderNode

function WindowEdgeFade({ edge }: { edge: "top" | "bottom" }) {
const EDGE_BLUR_LAYERS = [
{ std: 5, opaque: 0, fade: 24 },
{ std: 3, opaque: 0, fade: 44 },
{ std: 2, opaque: 10, fade: 66 },
{ std: 1, opaque: 28, fade: 84 },
{ std: 0.5, opaque: 50, fade: 100 },
] as const

function DocsEdgeBlurDefs({ uid }: { uid: string }) {
return (
<svg
className="pointer-events-none absolute size-0 overflow-hidden"
aria-hidden
>
<defs>
{EDGE_BLUR_LAYERS.map((layer, i) => (
<filter
key={i}
id={`docs-edge-blur-${i}-${uid}`}
x="-10%"
y="-50%"
width="120%"
height="200%"
colorInterpolationFilters="sRGB"
>
<feGaussianBlur in="SourceGraphic" stdDeviation={layer.std} />
</filter>
))}
</defs>
</svg>
)
}

function WindowEdgeFade({
edge,
uid,
}: {
edge: "top" | "bottom"
uid: string
}) {
const isTop = edge === "top"
const dir = isTop ? "to bottom" : "to top"

return (
<>
<div
aria-hidden
className={cn(
"pointer-events-none absolute inset-x-0 z-10 h-24",
isTop
? "top-0 bg-linear-to-b from-background from-25% via-background/60 to-transparent"
: "bottom-0 bg-linear-to-t from-background from-25% via-background/60 to-transparent"
)}
/>
<div
aria-hidden
className={cn(
"pointer-events-none absolute inset-x-0 z-10 h-16 backdrop-blur-md",
isTop
? "top-0 mask-[linear-gradient(to_bottom,black,transparent)]"
: "bottom-0 mask-[linear-gradient(to_top,black,transparent)]"
)}
/>
</>
<div
aria-hidden
className={cn(
"pointer-events-none absolute inset-x-0 z-10 h-16",
isTop ? "top-0 rounded-t-2xl" : "bottom-0 rounded-b-2xl"
)}
>
{EDGE_BLUR_LAYERS.map((layer, i) => {
const mask = `linear-gradient(${dir}, black ${layer.opaque}%, transparent ${layer.fade}%)`
return (
<div
key={i}
className="absolute inset-0"
style={{
backdropFilter: `url(#docs-edge-blur-${i}-${uid})`,
WebkitBackdropFilter: `url(#docs-edge-blur-${i}-${uid})`,
maskImage: mask,
WebkitMaskImage: mask,
}}
/>
)
})}
</div>
)
}

Expand All @@ -83,6 +132,7 @@ export function DocsShell({
}) {
const pathname = usePathname()
const scrollRef = useRef<HTMLDivElement>(null)
const blurUid = useId().replace(/:/g, "")

useEffect(() => {
scrollRef.current?.scrollTo({ top: 0 })
Expand Down Expand Up @@ -137,8 +187,9 @@ export function DocsShell({
>
{children}
</div>
<WindowEdgeFade edge="top" />
<WindowEdgeFade edge="bottom" />
<DocsEdgeBlurDefs uid={blurUid} />
<WindowEdgeFade edge="top" uid={blurUid} />
<WindowEdgeFade edge="bottom" uid={blurUid} />
<header className="absolute inset-x-0 top-0 z-20 flex h-14 items-center gap-3 px-4 md:px-6">
<DocsSidebarTrigger showWhenCollapsed />
<div className="ml-auto flex items-center gap-1">
Expand Down
110 changes: 110 additions & 0 deletions content/docs/components/folio.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
title: Folio
description: The page tips one way as you scroll down, the other as you scroll up, then springs flat when you stop.
---

import { FolioDemo } from "@/registry/folio/folio-demo"

A single sheet. Scroll down and it tips a few degrees one way; scroll up and it tips the other. The lean stays around 16° at a flick so a long page does not swing through the camera. Blur rides the traveling edge. Stop and it springs to the original plane. At the bottom the down-lean lets go; at the top the up-lean does.

<FolioDemo />

## Installation

<CliCommand item="folio" />

Or install straight from the public GitHub source registry:

<CliCommand item="folio" github />

## Usage

Use it as the scroll viewport. Children are the page. Wheel, touch, or trackpad tilts the sheet with the scroll — down one way, up the other. Idle returns it. Honors `prefers-reduced-motion` (no tilt, no blur).

<FrameworkCode>
<FrameworkReact>
```tsx
"use client"

import { Folio } from "@/components/ui/folio"

export function Page() {
return (
<Folio className="h-svh bg-background">
<main className="px-10 py-24 text-foreground">
<h1 className="text-5xl font-medium tracking-tight">
The page leans back
</h1>
<p className="mt-6 max-w-md text-sm leading-relaxed text-foreground/70">
Put the site in children. Folio is the scroller.
</p>
</main>
</Folio>
)
}
```
</FrameworkReact>
<FrameworkSvelte>
```svelte
<script>
import Folio from "$lib/components/ui/folio.svelte"
</script>

<Folio class="h-svh bg-background">
<main class="px-10 py-24 text-foreground">
<h1 class="text-5xl font-medium tracking-tight">
The page leans back
</h1>
<p class="mt-6 max-w-md text-sm leading-relaxed text-foreground/70">
Put the site in children. Folio is the scroller.
</p>
</main>
</Folio>
```
</FrameworkSvelte>
</FrameworkCode>

On a full document (window scroll), pass `windowScroll` and mark the page root with `data-folio-page`:

<FrameworkCode>
<FrameworkReact>
```tsx
"use client"

import { Folio } from "@/components/ui/folio"

export function App() {
return (
<>
<div data-folio-page>{/* page content */}</div>
<Folio windowScroll />
</>
)
}
```
</FrameworkReact>
<FrameworkSvelte>
```svelte
<script>
import Folio from "$lib/components/ui/folio.svelte"
</script>

<div data-folio-page><!-- page content --></div>
<Folio windowScroll />
```
</FrameworkSvelte>
</FrameworkCode>

## Props

| Prop | Type | Default |
| --- | --- | --- |
| `children` | `ReactNode` / snippet | — |
| `blur` | `number` | `4` — peak blur on the traveling edge |
| `perspective` | `number` | `1000` — floor `1000` |
| `returnMs` | `number` | `520` — time for the lean to come back after the bottom |
| `windowScroll` | `boolean` | `false` |
| `contentSelector` | `string` | `[data-folio-page]` |
| `label` | `string` | `Tilting page` |
| `className` | `string` | React only |
| `class` | `string` | Svelte only |
2 changes: 2 additions & 0 deletions content/docs/components/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"ascii-logo",
"---Pages---",
"dithered-404",
"---Sections---",
"folio",
"---Components---",
"gooey-color-picker"
]
Expand Down
1 change: 1 addition & 0 deletions content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ Most component kits hand you every option. 23rd picks a direction: spacing, radi
- [Phosphor Score](/docs/components/phosphor-score) — vertical CRT sheet music that falls and flares
- [Radiant Lines](/docs/components/radiant-lines) — hyperspace starfield background
- [Dithered 404](/docs/components/dithered-404) — Bayer-pixel 404 that burns under a fireball cursor
- [Folio](/docs/components/folio) — the page leans back on scroll, then springs flat
15 changes: 15 additions & 0 deletions public/r/folio-svelte.json

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions public/r/folio.json

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions public/r/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,32 @@
],
"type": "registry:ui"
},
{
"name": "folio",
"title": "Folio",
"description": "The whole page leans back in perspective while you scroll, blurs, then springs flat when you stop.",
"files": [
{
"path": "registry/folio/folio.tsx",
"type": "registry:ui",
"target": "components/ui/folio.tsx"
}
],
"type": "registry:ui"
},
{
"name": "folio-svelte",
"title": "Folio",
"description": "The whole page leans back in perspective while you scroll, blurs, then springs flat when you stop.",
"files": [
{
"path": "registry/folio/folio.svelte",
"type": "registry:file",
"target": "src/lib/components/ui/folio.svelte"
}
],
"type": "registry:ui"
},
{
"name": "gooey-color-picker",
"title": "Gooey Color Picker",
Expand Down
1 change: 1 addition & 0 deletions registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"registry/ascii-fluid/registry.json",
"registry/ascii-logo/registry.json",
"registry/dithered-404/registry.json",
"registry/folio/registry.json",
"registry/gooey-color-picker/registry.json",
"registry/live-orb/registry.json",
"registry/logo-burst/registry.json",
Expand Down
Loading
Loading