This is an automated email from the ASF dual-hosted git repository. erickguan pushed a commit to branch website-polish in repository https://gitbox.apache.org/repos/asf/opendal.git
commit a65f91679cbbcf2cb6a25aac86680a099a63f135 Author: Erick Guan <[email protected]> AuthorDate: Sun Jul 12 22:04:58 2026 +0800 Polish docs and services layout and styles --- website/src/components/landing/CodeTabs.jsx | 2 +- website/src/components/landing/sections.jsx | 6 +- website/src/components/landing/styles.module.css | 6 +- website/src/components/services/ServicePage.jsx | 180 +++++++++++++++------ website/src/components/services/styles.module.css | 154 ++++++++++++++---- website/src/components/ui/Breadcrumb.jsx | 51 ++++++ website/src/components/ui/CodeWindow.jsx | 6 +- website/src/components/ui/Tabs.jsx | 4 +- .../ui/breadcrumb.module.css} | 23 +-- website/src/components/ui/code-window.module.css | 7 +- website/src/css/footer.css | 7 +- website/src/css/global.css | 9 ++ website/src/theme/DocBreadcrumbs/index.jsx | 124 ++++++++++++++ 13 files changed, 468 insertions(+), 111 deletions(-) diff --git a/website/src/components/landing/CodeTabs.jsx b/website/src/components/landing/CodeTabs.jsx index e43a0a6ed..395ce3c9a 100644 --- a/website/src/components/landing/CodeTabs.jsx +++ b/website/src/components/landing/CodeTabs.jsx @@ -162,7 +162,7 @@ export default function CodeTabs({ samples, title, equalize = false }) { getId={(sample) => sample.id} getLabel={(sample) => sample.label} className={styles.codeTabs} - ariaLabel="Choose a language" + aria-label="Choose a language" controlsId={`${baseId}-panel`} id={baseId} /> diff --git a/website/src/components/landing/sections.jsx b/website/src/components/landing/sections.jsx index c68fd502e..af5cf91df 100644 --- a/website/src/components/landing/sections.jsx +++ b/website/src/components/landing/sections.jsx @@ -108,7 +108,7 @@ export function UsedBy() { </div> <ul className={styles.logoWall}> {usedBy.map((u) => ( - <li key={u.name}> + <li className={styles.logoWallItem} key={u.name}> <Link className={styles.logoItem} to={u.href} title={u.name}> <img className={styles.logoMark} @@ -122,7 +122,7 @@ export function UsedBy() { </Link> </li> ))} - <li> + <li className={styles.logoWallItem}> <Link className={styles.actionLogo} to={USERS_LIST_URL}> + Add your logo </Link> @@ -221,6 +221,7 @@ export function Services() { key={s.name} > <img + className={styles.serviceChipIcon} src={withBaseUrl(s.icon)} alt="" width="15" @@ -264,6 +265,7 @@ export function Bindings() { {bindings.map((b) => ( <Link className={styles.bindingCard} to={b.doc} key={b.name}> <img + className={styles.bindingIcon} src={withBaseUrl(b.icon)} alt="" width="30" diff --git a/website/src/components/landing/styles.module.css b/website/src/components/landing/styles.module.css index f45dc5bbf..f0c8954a5 100644 --- a/website/src/components/landing/styles.module.css +++ b/website/src/components/landing/styles.module.css @@ -186,7 +186,7 @@ grid-template-columns: repeat(auto-fill, minmax(12.5rem, 1fr)); gap: var(--odl-space-3); } -.logoWall > li { +.logoWallItem { display: flex; } .logoItem { @@ -347,7 +347,7 @@ color: var(--odl-fg); text-decoration: none; } -.serviceChip img { +.serviceChipIcon { width: 15px; height: 15px; object-fit: contain; @@ -386,7 +386,7 @@ background: var(--odl-surface-2); text-decoration: none; } -.bindingCard img { +.bindingIcon { width: 30px; height: 30px; object-fit: contain; diff --git a/website/src/components/services/ServicePage.jsx b/website/src/components/services/ServicePage.jsx index a95fbb464..35480d4ea 100644 --- a/website/src/components/services/ServicePage.jsx +++ b/website/src/components/services/ServicePage.jsx @@ -20,11 +20,22 @@ import React, { useEffect, useState } from "react"; import Layout from "@theme/Layout"; import CodeBlock from "@theme/CodeBlock"; -import Link from "@docusaurus/Link"; import { useLocation } from "@docusaurus/router"; +import Breadcrumb from "../ui/Breadcrumb"; import Tabs from "../ui/Tabs"; import styles from "./styles.module.css"; +const LANGUAGE_ALIASES = { + "c++": "cpp", + cplusplus: "cpp", + cpp: "cpp", + cxx: "cpp", +}; + +function cx(...classNames) { + return classNames.filter(Boolean).join(" "); +} + // Renders inline `code` spans inside an otherwise plain doc comment so the // reference table stays readable without pulling in a full markdown renderer. function renderComment(text) { @@ -44,45 +55,70 @@ function ConfigTable({ rows }) { return ( <div className={styles.tableWrap}> <table className={styles.table}> - <thead> - <tr> - <th>Key</th> - <th>Type</th> - <th>Required</th> - <th>Description</th> + <thead className={styles.tableHead}> + <tr className={styles.tableHeaderRow}> + <th className={styles.tableHeaderCell} scope="col"> + Key + </th> + <th className={styles.tableHeaderCell} scope="col"> + Type + </th> + <th className={styles.tableHeaderCell} scope="col"> + Required + </th> + <th className={styles.tableHeaderCell} scope="col"> + Description + </th> </tr> </thead> - <tbody> - {rows.map((c) => ( - <tr key={c.name} className={c.deprecated ? styles.deprecatedRow : ""}> - <td> - <code>{c.name}</code> - {c.deprecated && <span className={styles.badge}>deprecated</span>} - {c.default != null && ( - <span className={styles.chip}> - default <code>{c.default}</code> - </span> - )} - {c.default == null && c.example != null && ( - <span className={styles.chip}> - e.g. <code>{c.example}</code> - </span> - )} - </td> - <td data-label="Type">{c.type}</td> - <td data-label="Required">{c.required ? "yes" : "no"}</td> - <td className={styles.desc} data-label="Description"> - {renderComment(c.comments)} - {c.deprecated && c.deprecated.note && ( - <div className={styles.deprecatedNote}> - Deprecated - {c.deprecated.since ? ` since ${c.deprecated.since}` : ""}:{" "} - {renderComment(c.deprecated.note)} - </div> - )} - </td> - </tr> - ))} + <tbody className={styles.tableBody}> + {rows.map((c) => { + const rowClassName = cx( + styles.tableRow, + c.deprecated ? styles.deprecatedRow : "" + ); + + return ( + <tr key={c.name} className={rowClassName}> + <td className={cx(styles.tableCell, styles.tableKeyCell)}> + <code className={styles.configKey}>{c.name}</code> + {c.deprecated && ( + <span className={styles.badge}>deprecated</span> + )} + {c.default != null && ( + <span className={styles.chip}> + default <code className={styles.chipCode}>{c.default}</code> + </span> + )} + {c.default == null && c.example != null && ( + <span className={styles.chip}> + e.g. <code className={styles.chipCode}>{c.example}</code> + </span> + )} + </td> + <td className={styles.tableCell} data-label="Type"> + {c.type} + </td> + <td className={styles.tableCell} data-label="Required"> + {c.required ? "yes" : "no"} + </td> + <td + className={cx(styles.tableCell, styles.desc)} + data-label="Description" + > + {renderComment(c.comments)} + {c.deprecated && c.deprecated.note && ( + <div className={styles.deprecatedNote}> + {`Deprecated${ + c.deprecated.since ? ` since ${c.deprecated.since}` : "" + }: `} + {renderComment(c.deprecated.note)} + </div> + )} + </td> + </tr> + ); + })} </tbody> </table> </div> @@ -99,6 +135,52 @@ function bindingFromPath(pathname, scheme, available) { return tail && available.includes(tail) ? tail : undefined; } +function normalizeLanguage(value) { + const normalized = value.trim().toLowerCase(); + return LANGUAGE_ALIASES[normalized] || normalized; +} + +function safeDecodeURIComponent(value) { + try { + return decodeURIComponent(value); + } catch { + return undefined; + } +} + +// Keep raw `+` characters intact so links like `?language=c++` select C++. +// URLSearchParams decodes `+` as a space because it follows form encoding. +function languageFromSearch(search) { + const query = search.startsWith("?") ? search.slice(1) : search; + for (const part of query.split("&")) { + const separator = part.indexOf("="); + const rawKey = separator === -1 ? part : part.slice(0, separator); + const rawValue = separator === -1 ? "" : part.slice(separator + 1); + const key = safeDecodeURIComponent(rawKey); + if (key !== "language") { + continue; + } + + return safeDecodeURIComponent(rawValue); + } + + return undefined; +} + +function bindingFromSearch(search, examples) { + const language = languageFromSearch(search); + if (!language) { + return undefined; + } + + const normalized = normalizeLanguage(language); + return examples.find( + (example) => + normalizeLanguage(example.binding) === normalized || + normalizeLanguage(example.language) === normalized + )?.binding; +} + export default function ServicePage({ data }) { const { bindings, service } = data; const location = useLocation(); @@ -106,14 +188,15 @@ export default function ServicePage({ data }) { const labelOf = Object.fromEntries(bindings.map((b) => [b.id, b.label])); const available = service.examples.map((e) => e.binding); const initial = + bindingFromSearch(location.search, service.examples) || bindingFromPath(location.pathname, service.scheme, available) || available[0]; const [active, setActive] = useState(initial); useEffect(() => { setActive(initial); - // Re-sync when navigating between deep-link pages on the client. - }, [location.pathname]); + // Re-sync when navigating between deep-link pages or URL-selected tabs. + }, [location.pathname, location.search]); const example = service.examples.find((e) => e.binding === active); @@ -123,11 +206,12 @@ export default function ServicePage({ data }) { return ( <Layout title={title} description={description}> <div className={styles.page}> - <nav className={styles.breadcrumb} aria-label="Breadcrumb"> - <Link to="/services">Services</Link> - <span aria-hidden="true"> / </span> - <span>{service.scheme}</span> - </nav> + <Breadcrumb + aria-label="Breadcrumb" + rootLabel="Services" + rootHref="/services" + items={[{ label: service.scheme }]} + /> <header className={styles.header}> <h1 className={styles.title}>{service.scheme}</h1> @@ -146,7 +230,7 @@ export default function ServicePage({ data }) { onChange={(e) => setActive(e.binding)} getId={(e) => e.binding} getLabel={(e) => labelOf[e.binding] || e.binding} - ariaLabel="Choose a binding" + aria-label="Choose a binding" controlsId="service-example-panel" id="service-example-tabs" /> @@ -162,7 +246,9 @@ export default function ServicePage({ data }) { </div> <details className={styles.details}> - <summary>All configuration options (copy & trim)</summary> + <summary className={styles.detailsSummary}> + All configuration options (copy & trim) + </summary> <CodeBlock language={example.language} title="Full reference"> {example.full} </CodeBlock> diff --git a/website/src/components/services/styles.module.css b/website/src/components/services/styles.module.css index 89c974922..e0334c8c8 100644 --- a/website/src/components/services/styles.module.css +++ b/website/src/components/services/styles.module.css @@ -24,12 +24,6 @@ overflow-wrap: anywhere; } -.breadcrumb { - font-size: 0.9rem; - color: var(--ifm-color-emphasis-600); - margin-bottom: 1rem; -} - .header { margin-bottom: 2rem; } @@ -113,7 +107,7 @@ margin-top: 0.5rem; } -.details > summary { +.detailsSummary { cursor: pointer; font-weight: 500; padding: 0.5rem 0; @@ -126,23 +120,39 @@ color: var(--ifm-color-emphasis-600); } +.page :global(.theme-code-block), +.page :global([class*="codeBlockContainer"]) { + max-width: 100%; +} + .group { - border: 1px solid var(--ifm-color-emphasis-200); - border-radius: var(--ifm-global-radius); + border: 1px solid var(--odl-code-border); + border-radius: var(--odl-radius-md); margin-bottom: 0.75rem; overflow: hidden; + background: var(--ifm-background-color); + background-clip: padding-box; } .groupSummary { cursor: pointer; - font-weight: 600; + font-size: 0.95rem; + font-weight: 700; padding: 0.7rem 1rem; - background: var(--ifm-color-emphasis-100); + background: var(--odl-code-bg); + color: var(--ifm-heading-color); + border-radius: calc(var(--odl-radius-md) - 1px); display: flex; align-items: center; gap: 0.5rem; } +.group[open] > .groupSummary { + border-bottom: 1px solid var(--odl-code-border); + border-radius: calc(var(--odl-radius-md) - 1px) + calc(var(--odl-radius-md) - 1px) 0 0; +} + .groupCount { font-size: 0.75rem; font-weight: 500; @@ -164,7 +174,7 @@ white-space: nowrap; } -.chip code { +.chipCode { font-size: 0.72rem; background: transparent; border: none; @@ -172,40 +182,87 @@ } .tableWrap { + margin: 0; overflow-x: auto; + overflow-y: hidden; max-width: 100%; -webkit-overflow-scrolling: touch; + background: var(--ifm-background-color); } .table { + --service-table-border: var(--odl-code-border); + --service-table-border-strong: var(--odl-border-strong); + --service-table-stripe: var(--odl-code-bg); + width: 100%; min-width: 720px; + margin: 0; display: table; - border-collapse: collapse; + border: 0; + border-collapse: separate; + border-spacing: 0; + background: transparent; +} + +.tableHeaderRow { + border-top: 0; + background: transparent; } -.table th, -.table td { +.tableRow { + border-top: 0; + background: transparent; +} + +.tableHeaderCell, +.tableCell { text-align: left; vertical-align: top; padding: 0.6rem 0.75rem; - border-bottom: 1px solid var(--ifm-color-emphasis-200); + border: 0; + background: transparent; + overflow-wrap: normal; + word-break: normal; } -.table th { +.tableHeaderCell { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--ifm-color-emphasis-600); + border-bottom: 1px solid var(--service-table-border); +} + +.tableRow:not(:last-child) .tableCell { + border-bottom: 1px solid var(--service-table-border); +} + +.tableHeaderCell:not(:last-child), +.tableCell:not(:last-child) { + border-right: 1px solid var(--service-table-border); +} + +.tableHeaderCell:nth-child(2), +.tableCell:nth-child(2) { + min-width: 5.5rem; + white-space: nowrap; +} + +.tableHeaderCell:nth-child(3), +.tableCell:nth-child(3) { + min-width: 6.75rem; + white-space: nowrap; } .desc { white-space: pre-wrap; + overflow-wrap: anywhere; font-size: 0.9rem; color: var(--ifm-color-emphasis-800); } -.deprecatedRow code { +.deprecatedRow .configKey { text-decoration: line-through; } @@ -230,6 +287,22 @@ padding: 1.75rem 0.875rem 3rem; } + .page :global(.theme-code-block), + .page :global([class*="codeBlockContainer"]) { + overflow: hidden; + } + + .page :global(.prism-code) { + white-space: pre-wrap; + overflow-wrap: anywhere; + word-break: break-word; + } + + .page :global(.token-line) { + white-space: inherit; + overflow-wrap: inherit; + } + .grid { grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr)); gap: 0.75rem; @@ -239,6 +312,10 @@ padding: 0.85rem 0.9rem; } + .groupSummary { + padding: 0.65rem 0.875rem; + } + .tableWrap { overflow-x: visible; } @@ -248,28 +325,45 @@ display: block; } - .table thead { + .tableHead { display: none; } - .table tbody, - .table tr, - .table td { + .tableBody, + .tableRow, + .tableCell { + box-sizing: border-box; display: block; width: 100%; } - .table tr { - padding: 0.8rem 0.875rem; - border-bottom: 1px solid var(--ifm-color-emphasis-200); + .tableBody > .tableRow { + padding: 0; + border-bottom: 0; + background-color: var(--ifm-background-color); } - .table td { - padding: 0.25rem 0; - border-bottom: 0; + .tableBody > .tableRow:nth-child(2n) { + background-color: var(--service-table-stripe); + } + + .tableRow > .tableCell { + border-bottom: 1px solid var(--service-table-border-strong); + border-right: 0; + } + + .tableCell { + padding: 0.28rem 0.875rem; + background-color: transparent; + } + + .tableCell:nth-child(2), + .tableCell:nth-child(3) { + min-width: 0; + white-space: normal; } - .table td::before { + .tableCell::before { content: attr(data-label); display: block; margin-bottom: 0.1rem; @@ -279,7 +373,7 @@ color: var(--ifm-color-emphasis-600); } - .table td:first-child::before { + .tableKeyCell::before { content: "Key"; } diff --git a/website/src/components/ui/Breadcrumb.jsx b/website/src/components/ui/Breadcrumb.jsx new file mode 100644 index 000000000..de140805b --- /dev/null +++ b/website/src/components/ui/Breadcrumb.jsx @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from "react"; +import Link from "@docusaurus/Link"; +import styles from "./breadcrumb.module.css"; + +export default function Breadcrumb({ + rootLabel, + rootHref, + items, + className = "", + ...navProps +}) { + const classNames = [styles.breadcrumb, className].filter(Boolean).join(" "); + + return ( + <nav {...navProps} className={classNames}> + <Link to={rootHref}>{rootLabel}</Link> + {items.map((item, index) => { + const isLast = index === items.length - 1; + return ( + <React.Fragment key={`${item.label}-${index}`}> + <span aria-hidden="true"> / </span> + {!isLast && item.href ? ( + <Link to={item.href}>{item.label}</Link> + ) : ( + <span>{item.label}</span> + )} + </React.Fragment> + ); + })} + </nav> + ); +} diff --git a/website/src/components/ui/CodeWindow.jsx b/website/src/components/ui/CodeWindow.jsx index 6c95a37eb..ba2f6283f 100644 --- a/website/src/components/ui/CodeWindow.jsx +++ b/website/src/components/ui/CodeWindow.jsx @@ -24,9 +24,9 @@ import styles from "./code-window.module.css"; function CodeWindowDots() { return ( <div className={styles.windowDots} aria-hidden="true"> - <span /> - <span /> - <span /> + <span className={styles.windowDot} /> + <span className={styles.windowDot} /> + <span className={styles.windowDot} /> </div> ); } diff --git a/website/src/components/ui/Tabs.jsx b/website/src/components/ui/Tabs.jsx index 6fbe7c475..90cf10b15 100644 --- a/website/src/components/ui/Tabs.jsx +++ b/website/src/components/ui/Tabs.jsx @@ -26,10 +26,10 @@ export default function Tabs({ onChange, getId = (item) => item.id, getLabel = (item) => item.label, - ariaLabel, controlsId, id, className = "", + ...tabListProps }) { const generatedId = useId(); const baseId = id || generatedId; @@ -74,9 +74,9 @@ export default function Tabs({ return ( <div + {...tabListProps} className={[styles.tabs, className].filter(Boolean).join(" ")} role="tablist" - aria-label={ariaLabel} onKeyDown={onKeyDown} > {items.map((item, index) => { diff --git a/website/src/css/footer.css b/website/src/components/ui/breadcrumb.module.css similarity index 69% copy from website/src/css/footer.css copy to website/src/components/ui/breadcrumb.module.css index ff47cf001..316498c5f 100644 --- a/website/src/css/footer.css +++ b/website/src/components/ui/breadcrumb.module.css @@ -17,22 +17,9 @@ * under the License. */ -.footer { - background-color: var(--odl-bg-subtle); - border-top: 1px solid var(--odl-border); -} - -.footer__copyright { - font-size: 0.75em; - color: var(--odl-fg-subtle); - margin-top: var(--odl-space-4); -} - -.footer__logo { - margin-top: unset; - max-width: 280px; -} - -[data-theme="dark"] .footer__logo { - filter: brightness(0) invert(1) opacity(0.7); +.breadcrumb { + font-size: 0.9rem; + line-height: var(--ifm-line-height-base); + color: var(--ifm-color-emphasis-600); + margin-bottom: 1rem; } diff --git a/website/src/components/ui/code-window.module.css b/website/src/components/ui/code-window.module.css index ecb07e687..4a6e2f413 100644 --- a/website/src/components/ui/code-window.module.css +++ b/website/src/components/ui/code-window.module.css @@ -40,7 +40,7 @@ display: flex; gap: var(--code-window-dot-gap); } -.windowDots span { +.windowDot { width: var(--code-window-dot-size); height: var(--code-window-dot-size); border-radius: var(--odl-radius-pill); @@ -97,13 +97,13 @@ } .codeBody :global(.theme-code-block), -.codeBody :global(div[class*="codeBlockContainer"]) { +.codeBody :global([class*="codeBlockContainer"]) { margin: 0; border: 0; border-radius: 0; box-shadow: none; } -.codeBody :global(pre) { +.codeBody :global(.prism-code) { border: 0 !important; border-radius: 0 !important; margin: 0; @@ -112,7 +112,6 @@ white-space: pre-wrap; overflow-wrap: anywhere; } -.codeBody :global(pre code), .codeBody :global(.token-line) { white-space: inherit; overflow-wrap: inherit; diff --git a/website/src/css/footer.css b/website/src/css/footer.css index ff47cf001..aa5e47ec2 100644 --- a/website/src/css/footer.css +++ b/website/src/css/footer.css @@ -18,10 +18,15 @@ */ .footer { - background-color: var(--odl-bg-subtle); + background-color: var(--odl-bg); border-top: 1px solid var(--odl-border); } +html.docs-wrapper .footer, +html.plugin-opendal-services-docs .footer { + background-color: var(--odl-bg-subtle); +} + .footer__copyright { font-size: 0.75em; color: var(--odl-fg-subtle); diff --git a/website/src/css/global.css b/website/src/css/global.css index 86447b783..4e9dd1c55 100644 --- a/website/src/css/global.css +++ b/website/src/css/global.css @@ -39,6 +39,15 @@ body { "ss01" 1; } +#__docusaurus, +.main-wrapper, +[class*="docsWrapper"], +[class*="docRoot"], +[class*="docMainContainer"], +[class*="docItemWrapper"] { + background: var(--odl-bg); +} + h1, h2, h3, diff --git a/website/src/theme/DocBreadcrumbs/index.jsx b/website/src/theme/DocBreadcrumbs/index.jsx new file mode 100644 index 000000000..8c53c10ed --- /dev/null +++ b/website/src/theme/DocBreadcrumbs/index.jsx @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from "react"; +import { translate } from "@docusaurus/Translate"; +import { useLocation } from "@docusaurus/router"; +import Breadcrumb from "@site/src/components/ui/Breadcrumb"; + +const ROOTS = { + docs: "Docs", + community: "Community", +}; + +const LABELS = { + core: "Rust", + cpp: "C++", + dotnet: ".NET", + nodejs: "Node.js", + ocaml: "OCaml", + php: "PHP", + "pmc-members": "PMC Members", +}; + +function labelFromSegment(segment) { + if (LABELS[segment]) { + return LABELS[segment]; + } + + return decodeURIComponent(segment) + .split(/[-_]+/) + .filter(Boolean) + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); +} + +function breadcrumbFromPath(pathname) { + const segments = pathname.split("/").filter(Boolean); + const rootIndex = segments.findIndex((segment) => ROOTS[segment]); + if (rootIndex === -1) { + return null; + } + + const root = segments[rootIndex]; + const pathSegments = segments.slice(rootIndex + 1); + const items = []; + + if (root === "docs" && pathSegments[0] === "core") { + items.push({ label: labelFromSegment("core"), href: "/docs/core" }); + pathSegments.slice(1).forEach((segment, index) => { + items.push({ + label: labelFromSegment(segment), + href: `/docs/core/${pathSegments.slice(1, index + 2).join("/")}`, + }); + }); + } else if (root === "docs" && pathSegments[0] === "bindings") { + const binding = pathSegments[1]; + if (binding) { + items.push({ + label: labelFromSegment(binding), + href: `/docs/bindings/${binding}`, + }); + pathSegments.slice(2).forEach((segment, index) => { + items.push({ + label: labelFromSegment(segment), + href: `/docs/bindings/${binding}/${pathSegments + .slice(2, index + 3) + .join("/")}`, + }); + }); + } + } else { + const current = pathSegments[pathSegments.length - 1] || "overview"; + items.push({ label: labelFromSegment(current) }); + } + + return { + rootLabel: ROOTS[root], + rootHref: `/${root}`, + items, + }; +} + +export default function DocBreadcrumbs() { + const location = useLocation(); + const breadcrumb = breadcrumbFromPath(location.pathname); + + if (!breadcrumb) { + return null; + } + + const items = breadcrumb.items.map((item, index) => ({ + ...item, + href: index === breadcrumb.items.length - 1 ? undefined : item.href, + })); + + return ( + <Breadcrumb + aria-label={translate({ + id: "theme.docs.breadcrumbs.navAriaLabel", + message: "Breadcrumbs", + description: "The ARIA label for the breadcrumbs", + })} + rootLabel={breadcrumb.rootLabel} + rootHref={breadcrumb.rootHref} + items={items} + /> + ); +}
