Files
onixbyte.com/theme/components/sections/FeatureGrid.tsx
T
zihluwang e149401346 feat(theme): add a capabilities section band
- Replace the techStack band with project-type capabilities
- Delete the TechStack component and its section type
- Give grid cards an optional status badge
- Pad --big buttons, which Rspress ships with none
- Revise the home, services and products copy
2026-09-29 12:21:25 +08:00

39 lines
1.5 KiB
TypeScript

import type { Feature } from "@rspress/core"
import { HomeFeature } from "@rspress/core/theme-original"
import { arr, str, type FeatureCard } from "./types"
/** The badge is interpolated into markup, so it cannot carry its own markup. */
function escapeHtml(value: string): string {
return value
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
}
/**
* Card grid used for both the homepage advantages and the products/services
* grids. Delegates to the stock `HomeFeature`, which already handles `span`,
* icons and the reveal animation — it accepts a `features` prop precisely so it
* can be driven from somewhere other than page frontmatter.
*
* A card's `badge` is folded into the front of `details` as a span, because
* `HomeFeature` renders `details` through `renderHtmlOrText` (which passes HTML
* through) and exposes no other slot. Note this makes the whole `details` string
* render as HTML for badged cards — frontmatter is first-party, and
* `renderHtmlOrText` already does this for any string containing a tag.
*/
export function FeatureGrid({ items }: { items?: FeatureCard[] }) {
const features = arr<FeatureCard>(items)?.map(({ badge, ...feature }) => {
const label = str(badge)
if (!label) return feature as Feature
const details = str(feature.details) ?? ""
return {
...feature,
details: `<span class="rp-card-badge">${escapeHtml(label)}</span>${details}`,
} as Feature
})
if (!features) return null
return <HomeFeature features={features} />
}