import { useRef, useState } from "react";
import {
Badge,
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
Descriptions,
ScrollArea,
} from "@godxjp/ui/data-display";
import { Heading, Paragraph, Text } from "@godxjp/ui/general";
import { Flex, PageContainer } from "@godxjp/ui/layout";
import { Anchor, type AnchorItemProp } from "@godxjp/ui/navigation";
/**
* Anchor — Ant Design `Anchor` (6.6.5). The in-page section navigation, and the only thing in the
* library that COMPUTES which section is current.
*
* This page is built out of the cases that break a table of contents, not the one that flatters it:
*
* — a FORTY-entry rail beside a TWO-entry one, in the same screen, because the rail that works at
* two entries is not the rail that works at forty (it scrolls, and the ink rail has to keep up);
* — a 71-character UNBREAKABLE label, which is a real section title (a config key, a German
* compound, a URL) and the shape that widens a rail until the page grows a scrollbar;
* — CJK beside Latin in the same list, where the same nominal size is a taller line box;
* — a section TALLER than its scrollport, which is where an IntersectionObserver band goes blank
* and this component keeps answering;
* — the HORIZONTAL direction, which is what a narrow viewport collapses to;
* — and a bounded `getContainer`, because a page is very often not the thing that scrolls.
*
* Composed only from real @godxjp/ui components.
*/
/** A real section title that cannot be broken anywhere. Exactly 71 characters. */
const UNBREAKABLE = "RegionalDeploymentConfigurationParameterNamespaceIdentifierOverrides123";
const PRIMER: AnchorItemProp[] = [
{ key: "p1", href: "#primer-what", title: "What it resolves" },
{ key: "p2", href: "#primer-why", title: "Why not sticky" },
];
const CHAPTERS = [
{ id: "ch-intro", latin: "Introduction", cjk: "はじめに" },
{ id: "ch-scope", latin: "Scope and audience", cjk: "対象範囲と読者" },
{ id: "ch-terms", latin: "Defined terms", cjk: "用語の定義" },
{ id: "ch-accounts", latin: "Accounts", cjk: "アカウント" },
{ id: "ch-identity", latin: "Identity verification", cjk: "本人確認" },
{ id: "ch-billing", latin: "Billing", cjk: "請求" },
{ id: "ch-invoices", latin: "Invoices and receipts", cjk: "請求書と領収書" },
{ id: "ch-taxes", latin: "Taxes", cjk: "税" },
{ id: "ch-refunds", latin: "Refunds", cjk: "返金" },
{ id: "ch-plans", latin: "Plans", cjk: "プラン" },
{ id: "ch-limits", latin: "Usage limits", cjk: "利用上限" },
{ id: "ch-fair-use", latin: "Fair use", cjk: "公正利用" },
{ id: "ch-data", latin: "Data ownership", cjk: "データの帰属" },
{ id: "ch-retention", latin: "Retention", cjk: "保持期間" },
{ id: "ch-deletion", latin: "Deletion", cjk: "削除" },
{ id: "ch-export", latin: "Export", cjk: "エクスポート" },
{ id: "ch-security", latin: "Security", cjk: "セキュリティ" },
{ id: "ch-incident", latin: "Incident response", cjk: "インシデント対応" },
{ id: "ch-subprocessors", latin: "Sub-processors", cjk: "再委託先" },
{ id: "ch-transfers", latin: "International transfers", cjk: "国際移転" },
{ id: "ch-availability", latin: "Availability", cjk: "可用性" },
{ id: "ch-maintenance", latin: "Maintenance windows", cjk: "メンテナンス" },
{ id: "ch-support", latin: "Support", cjk: "サポート" },
{ id: "ch-escalation", latin: "Escalation", cjk: "エスカレーション" },
{ id: "ch-credits", latin: "Service credits", cjk: "サービスクレジット" },
{ id: "ch-acceptable", latin: "Acceptable use", cjk: "禁止事項" },
{ id: "ch-content", latin: "Customer content", cjk: "顧客コンテンツ" },
{ id: "ch-ip", latin: "Intellectual property", cjk: "知的財産" },
{ id: "ch-feedback", latin: "Feedback", cjk: "フィードバック" },
{ id: "ch-publicity", latin: "Publicity", cjk: "公表" },
{ id: "ch-warranty", latin: "Warranties", cjk: "保証" },
{ id: "ch-liability", latin: "Limitation of liability", cjk: "責任の制限" },
{ id: "ch-indemnity", latin: "Indemnity", cjk: "補償" },
{ id: "ch-term", latin: "Term", cjk: "契約期間" },
{ id: "ch-suspension", latin: "Suspension", cjk: "利用停止" },
{ id: "ch-termination", latin: "Termination", cjk: "解約" },
{ id: "ch-survival", latin: "Survival", cjk: "存続条項" },
{ id: "ch-law", latin: "Governing law", cjk: "準拠法" },
{ id: "ch-disputes", latin: "Disputes", cjk: "紛争解決" },
{ id: "ch-namespace", latin: UNBREAKABLE, cjk: "名前空間の上書き" },
{ id: "ch-changes", latin: "Changes to these terms", cjk: "本規約の変更" },
{ id: "ch-contact", latin: "Contact", cjk: "お問い合わせ" },
];
const CHAPTER_ITEMS: AnchorItemProp[] = CHAPTERS.map((chapter) => ({
key: chapter.id,
href: `#${chapter.id}`,
// CJK beside Latin on ONE row: the same nominal size, a taller line box, and the rail has to
// hold both without the ink rail losing its alignment.
title: (
<>
{chapter.cjk} · {chapter.latin}
>
),
}));
const HORIZONTAL: AnchorItemProp[] = [
{ key: "h-overview", href: "#bar-overview", title: "概要 · Overview" },
{ key: "h-pricing", href: "#bar-pricing", title: "価格 · Pricing" },
{ key: "h-security", href: "#bar-security", title: "セキュリティ · Security" },
{ key: "h-faq", href: "#bar-faq", title: "よくある質問 · FAQ" },
];
function Chapter({ id, title, lines }: { id: string; title: string; lines: number }) {
return (
{title}
{Array.from({ length: lines }, (_, line) => (
本条は{title}について定めます。The clause is deliberately long enough that a reader spends
several scroll gestures inside it, which is the case an IntersectionObserver band cannot
answer and a decision line can.
))}
);
}
export default function Demo() {
const primerRef = useRef(null);
const chaptersRef = useRef(null);
const barRef = useRef(null);
const [reading, setReading] = useState("");
return (
1 · Two entries, and what the component reports
The smallest rail there is, bounded by its own `getContainer` rather than the page.
`onValueChange` hands back the href the SCROLL POSITION resolved, which is the value
printed below it.
primerRef.current ?? window}
onValueChange={setReading}
/>
{reading || "—"} },
{
label: "aria-current",
children: location,
},
]}
/>
2 · Forty-two entries, CJK beside Latin, and a 71-character label
The rail that works at two entries is not the rail that works at forty-two: this one
scrolls, the ink rail keeps up, and the `{UNBREAKABLE.length}`-character unbreakable
title wraps instead of widening the column until the page grows a scrollbar.
chaptersRef.current ?? window}
/>
{CHAPTERS.map((chapter) => (
))}
3 · direction="horizontal" — the narrow case
One row that scrolls inline rather than wrapping onto a second, because the ink rail
has to stay beside the label it belongs to. Nesting is not available here, and Ant
Design says the same.
barRef.current ?? window}
/>
4 · Against the page, pinned by Affix
The default. `affix` is `true`, so the rail pins itself once it reaches the line;
`targetOffsetBlockStart` is the room a clicked section leaves for whatever is pinned
above it. Landing on one of these hashes selects the entry with no scroll event at
all, and the click moves FOCUS to the section, not just the scroll position.
);
}