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
129 changes: 121 additions & 8 deletions src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -660,16 +660,129 @@ article h4 {
float: none;
}

[class*="generatedIndexPage"] .row .col--6 {
flex: 0 0 100% !important;
max-width: 100% !important;
/* Style overview pages as bullet point list */
[class*="generatedIndexPage"] .row {
display: block !important;
list-style: none !important;
padding: 1rem 0 2rem 2rem !important;
}

[class*="generatedIndexPage"] .row {
display: flex !important;
flex-direction: column !important;
gap: 1rem !important;
align-items: stretch !important;
[class*="generatedIndexPage"] .col {
display: list-item !important;
list-style-type: disc !important;
list-style-position: outside !important;
margin-left: 1rem !important;
margin-bottom: 0.5rem !important;
padding: 0 !important;
color: #9ca3af !important;
cursor: pointer !important;
}

[data-theme="dark"] [class*="generatedIndexPage"] .col {
color: #6b7280 !important;
}

[class*="generatedIndexPage"] article,
[class*="generatedIndexPage"] .card,
[class*="generatedIndexPage"] .item {
all: unset !important;
display: inline !important;
}

[class*="generatedIndexPage"] a {
all: unset !important;
display: inline !important;
cursor: pointer !important;
text-decoration: none !important;
}

/* Style title inline with light blue color */
[class*="generatedIndexPage"] .cardTitle_rnsV,
[class*="generatedIndexPage"] h2 {
all: unset !important;
display: inline !important;
font-weight: 600 !important;
color: var(--ifm-color-primary) !important;
font-size: 1rem !important;
}

[data-theme="dark"] [class*="generatedIndexPage"] h2 {
color: var(--ifm-color-primary-lightest) !important;
}

[class*="generatedIndexPage"] h2::before {
content: "" !important;
}

/* Display description inline after heading */
[class*="generatedIndexPage"] p {
all: unset !important;
display: inline !important;
color: var(--ifm-font-color-base) !important;
font-size: 1rem !important;
}

/* Add colon separator between heading and description */
[class*="generatedIndexPage"] h2::after {
content: ": " !important;
font-weight: normal !important;
color: var(--ifm-font-color-base) !important;
}

/* Exclude pagination navigation from bullet list styling - use higher specificity */
nav.pagination-nav,
[class*="generatedIndexPage"] nav.pagination-nav {
display: grid !important;
grid-template-columns: repeat(2, 1fr) !important;
gap: var(--ifm-spacing-horizontal) !important;
padding: 0 !important;
list-style: none !important;
}

nav.pagination-nav .pagination-nav__link,
[class*="generatedIndexPage"] nav.pagination-nav .pagination-nav__link {
display: block !important;
line-height: var(--ifm-heading-line-height) !important;
padding: var(--ifm-global-spacing) !important;
border: 1px solid var(--ifm-color-emphasis-300) !important;
border-radius: var(--ifm-pagination-nav-border-radius) !important;
text-decoration: none !important;
transition: border-color var(--ifm-transition-fast)
var(--ifm-transition-timing-default) !important;
cursor: pointer !important;
}

nav.pagination-nav .pagination-nav__link--next {
text-align: right !important;
grid-column: 2 / 3 !important;
}

nav.pagination-nav .pagination-nav__link:hover,
[class*="generatedIndexPage"] nav.pagination-nav .pagination-nav__link:hover {
border-color: var(--ifm-color-primary) !important;
text-decoration: none !important;
}

nav.pagination-nav .pagination-nav__sublabel,
[class*="generatedIndexPage"] nav.pagination-nav .pagination-nav__sublabel {
display: block !important;
color: var(--ifm-color-content-secondary) !important;
font-size: var(--ifm-h5-font-size) !important;
font-weight: var(--ifm-font-weight-semibold) !important;
margin-bottom: 0.25rem !important;
}

nav.pagination-nav .pagination-nav__label,
[class*="generatedIndexPage"] nav.pagination-nav .pagination-nav__label {
display: block !important;
color: var(--ifm-link-color) !important;
font-size: var(--ifm-h4-font-size) !important;
font-weight: var(--ifm-heading-font-weight) !important;
word-break: break-word !important;
}

nav.pagination-nav .pagination-nav__link *:last-child {
margin-bottom: 0 !important;
}

article,
Expand Down
186 changes: 186 additions & 0 deletions src/theme/DocCard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import React, { type ReactNode } from "react"
import clsx from "clsx"
import Link from "@docusaurus/Link"
import {
useDocById,
findFirstSidebarItemLink,
} from "@docusaurus/plugin-content-docs/client"
import { usePluralForm } from "@docusaurus/theme-common"
import isInternalUrl from "@docusaurus/isInternalUrl"
import { translate } from "@docusaurus/Translate"

import type { Props } from "@theme/DocCard"
import Heading from "@theme/Heading"
import type {
PropSidebarItemCategory,
PropSidebarItemLink,
} from "@docusaurus/plugin-content-docs"

import styles from "./styles.module.css"

function useCategoryItemsPlural() {
const { selectMessage } = usePluralForm()
return (count: number) =>
selectMessage(
count,
translate(
{
message: "1 item|{count} items",
id: "theme.docs.DocCard.categoryDescription.plurals",
description:
"The default description for a category card in the generated index about how many items this category includes",
},
{ count },
),
)
}

function CardContainer({
className,
href,
children,
}: {
className?: string
href: string
children: ReactNode
}): ReactNode {
return (
<Link
href={href}
className={clsx("card padding--lg", styles.cardContainer, className)}
>
{children}
</Link>
)
}

function CardLayout({
className,
href,
title,
description,
}: {
className?: string
href: string
title: string
description?: string
}): ReactNode {
return (
<CardContainer href={href} className={className}>
<Heading
as="h2"
className={clsx("text--truncate", styles.cardTitle)}
title={title}
>
{title}
</Heading>
{description && (
<p
className={clsx("text--truncate", styles.cardDescription)}
title={description}
>
{description}
</p>
)}
</CardContainer>
)
}

function NestedItem({
item,
}: { item: PropSidebarItemLink | PropSidebarItemCategory }) {
if (item.type === "link") {
const doc = useDocById(item.docId ?? undefined)
const description = item.description ?? doc?.description
return (
<li>
<Link to={item.href} className={styles.nestedLink}>
<span className={styles.nestedTitle}>{item.label}</span>
{description && (
<>
<span className={styles.nestedColon}>: </span>
<span className={styles.nestedDescription}>{description}</span>
</>
)}
</Link>
</li>
)
} else if (item.type === "category") {
const subHref = findFirstSidebarItemLink(item)
return subHref ? (
<li>
<Link to={subHref} className={styles.nestedLink}>
<span className={styles.nestedTitle}>{item.label}</span>
{item.description && (
<>
<span className={styles.nestedColon}>: </span>
<span className={styles.nestedDescription}>
{item.description}
</span>
</>
)}
</Link>
</li>
) : null
}
return null
}

function CardCategory({ item }: { item: PropSidebarItemCategory }): ReactNode {
const href = findFirstSidebarItemLink(item)

// Unexpected: categories that don't have a link have been filtered upfront
if (!href) {
return null
}

return (
<CardContainer href={href} className={item.className}>
<Heading
as="h2"
className={clsx("text--truncate", styles.cardTitle)}
title={item.label}
>
{item.label}
</Heading>
{item.description && (
<p
className={clsx("text--truncate", styles.cardDescription)}
title={item.description}
>
{item.description}
</p>
)}
{item.items.length > 0 && (
<ul className={styles.nestedList}>
{item.items.map((subItem, index) => (
<NestedItem key={index} item={subItem as any} />
))}
</ul>
)}
</CardContainer>
)
}

function CardLink({ item }: { item: PropSidebarItemLink }): ReactNode {
const doc = useDocById(item.docId ?? undefined)
return (
<CardLayout
className={item.className}
href={item.href}
title={item.label}
description={item.description ?? doc?.description}
/>
)
}

export default function DocCard({ item }: Props): ReactNode {
switch (item.type) {
case "link":
return <CardLink item={item} />
case "category":
return <CardCategory item={item} />
default:
throw new Error(`unknown item type ${JSON.stringify(item)}`)
}
}
62 changes: 62 additions & 0 deletions src/theme/DocCard/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
.cardContainer {
--ifm-link-color: var(--ifm-color-emphasis-800);
--ifm-link-hover-color: var(--ifm-color-emphasis-700);
--ifm-link-hover-decoration: none;

box-shadow: 0 1.5px 3px 0 rgb(0 0 0 / 15%);
border: 1px solid var(--ifm-color-emphasis-200);
transition: all var(--ifm-transition-fast) ease;
transition-property: border, box-shadow;
}

.cardContainer:hover {
border-color: var(--ifm-color-primary);
box-shadow: 0 3px 6px 0 rgb(0 0 0 / 20%);
}

.cardContainer *:last-child {
margin-bottom: 0;
}

.cardTitle {
font-size: 1.2rem;
}

.cardDescription {
font-size: 0.8rem;
}

.nestedList {
margin-top: 0;
padding-left: 1.5rem;
list-style-type: circle;
font-weight: 400;
color: var(--ifm-color-emphasis-900);
}

.nestedList a {
color: var(--ifm-link-color);
font-weight: 400;
}

.nestedLink {
text-decoration: none;
transition: color 0.2s ease;
}

.nestedTitle {
color: var(--ifm-color-primary);
font-weight: 600;
}

.nestedColon {
color: var(--ifm-font-color-base);
}

.nestedDescription {
color: var(--ifm-font-color-base);
}

.nestedLink:hover .nestedTitle {
color: var(--ifm-color-primary-dark);
}