feat(docs): group package catalog
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
- key: foundations
|
||||
order: 10
|
||||
label:
|
||||
zh-Hans: 基础能力
|
||||
en-US: Foundations
|
||||
|
||||
- key: application
|
||||
order: 20
|
||||
label:
|
||||
zh-Hans: 应用构建
|
||||
en-US: Application
|
||||
|
||||
- key: content
|
||||
order: 30
|
||||
label:
|
||||
zh-Hans: 内容与数据
|
||||
en-US: Content and data
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
type DocLocale,
|
||||
type DocLocation,
|
||||
type DocPackage,
|
||||
type DocPackageGroup,
|
||||
type DocPage,
|
||||
type DocSection,
|
||||
type DocTocItem,
|
||||
@@ -19,14 +20,37 @@ type DocContentModule = {
|
||||
}
|
||||
|
||||
type PackageDefinition = {
|
||||
group: string
|
||||
key: string
|
||||
label: string
|
||||
description: Record<DocLocale, string>
|
||||
order: number
|
||||
}
|
||||
|
||||
type PackageGroupDefinition = {
|
||||
key: string
|
||||
label: Record<DocLocale, string>
|
||||
order: number
|
||||
}
|
||||
|
||||
type ParsedDocPage = DocPage & { packageKey: string }
|
||||
|
||||
const packageGroupModules = import.meta.glob<unknown>("./package-groups.yaml", {
|
||||
eager: true,
|
||||
import: "default",
|
||||
})
|
||||
|
||||
const packageGroupDefinitions = Object.values(packageGroupModules)
|
||||
.flatMap(parsePackageGroupDefinitions)
|
||||
.toSorted(
|
||||
(left, right) =>
|
||||
left.order - right.order || left.key.localeCompare(right.key)
|
||||
)
|
||||
|
||||
const packageGroupsByKey = new Map(
|
||||
packageGroupDefinitions.map((group) => [group.key, group])
|
||||
)
|
||||
|
||||
const packageMetaModules = import.meta.glob<unknown>(
|
||||
"../../../packages/*/docs/meta.yaml",
|
||||
{ eager: true, import: "default" }
|
||||
@@ -34,10 +58,16 @@ const packageMetaModules = import.meta.glob<unknown>(
|
||||
|
||||
const packageDefinitions = Object.entries(packageMetaModules)
|
||||
.map(([path, metadata]) => parsePackageDefinition(path, metadata))
|
||||
.toSorted(
|
||||
(left, right) =>
|
||||
left.order - right.order || left.key.localeCompare(right.key)
|
||||
)
|
||||
.toSorted((left, right) => {
|
||||
const leftGroup = getPackageGroupDefinition(left.group)
|
||||
const rightGroup = getPackageGroupDefinition(right.group)
|
||||
|
||||
return (
|
||||
leftGroup.order - rightGroup.order ||
|
||||
left.order - right.order ||
|
||||
left.key.localeCompare(right.key)
|
||||
)
|
||||
})
|
||||
|
||||
const docModuleLoaders = import.meta.glob<DocContentModule>(
|
||||
"../../../packages/*/docs/*/**/*.mdx",
|
||||
@@ -67,12 +97,21 @@ const packagesByLocale: Record<DocLocale, readonly DocPackage[]> = {
|
||||
),
|
||||
}
|
||||
|
||||
const packageGroupsByLocale: Record<DocLocale, readonly DocPackageGroup[]> = {
|
||||
"zh-Hans": createDocPackageGroups("zh-Hans"),
|
||||
"en-US": createDocPackageGroups("en-US"),
|
||||
}
|
||||
|
||||
validateLocalizedPagePairs(packagesByLocale)
|
||||
|
||||
export function getDocPackages(locale: DocLocale = defaultDocLocale) {
|
||||
return packagesByLocale[locale]
|
||||
}
|
||||
|
||||
export function getDocPackageGroups(locale: DocLocale = defaultDocLocale) {
|
||||
return packageGroupsByLocale[locale]
|
||||
}
|
||||
|
||||
export function getDocHref(docPackage: DocPackage, page?: DocPage) {
|
||||
const prefix = getLocalePrefix(docPackage.locale)
|
||||
const target = page ?? getOverviewPage(docPackage)
|
||||
@@ -203,6 +242,7 @@ function createDocPackage(
|
||||
}
|
||||
|
||||
return {
|
||||
group: definition.group,
|
||||
key: definition.key,
|
||||
label: definition.label,
|
||||
description: definition.description[locale],
|
||||
@@ -211,6 +251,74 @@ function createDocPackage(
|
||||
}
|
||||
}
|
||||
|
||||
function createDocPackageGroups(locale: DocLocale): readonly DocPackageGroup[] {
|
||||
const packages = packagesByLocale[locale]
|
||||
|
||||
return packageGroupDefinitions.flatMap((definition) => {
|
||||
const groupPackages = packages.filter(
|
||||
(docPackage) => docPackage.group === definition.key
|
||||
)
|
||||
if (groupPackages.length === 0) return []
|
||||
|
||||
return {
|
||||
key: definition.key,
|
||||
label: definition.label[locale],
|
||||
packages: groupPackages,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function parsePackageGroupDefinitions(
|
||||
value: unknown
|
||||
): PackageGroupDefinition[] {
|
||||
if (!Array.isArray(value)) {
|
||||
throw new Error("Package documentation groups must be an array.")
|
||||
}
|
||||
|
||||
const groups = value.map((entry, index) => {
|
||||
const path = `package-groups.yaml[${index}]`
|
||||
if (!isRecord(entry)) {
|
||||
throw new Error(
|
||||
`Package documentation group must be an object: "${path}".`
|
||||
)
|
||||
}
|
||||
|
||||
const label = entry.label
|
||||
if (!isRecord(label)) {
|
||||
throw new Error(
|
||||
`Package documentation group requires a localized "label" object: "${path}".`
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
key: readMetaString(entry, "key", path),
|
||||
label: {
|
||||
"zh-Hans": readMetaString(label, "zh-Hans", path),
|
||||
"en-US": readMetaString(label, "en-US", path),
|
||||
},
|
||||
order: readMetaNumber(entry, "order", path),
|
||||
}
|
||||
})
|
||||
|
||||
const duplicate = groups.find(
|
||||
(group, index) =>
|
||||
groups.findIndex((item) => item.key === group.key) !== index
|
||||
)
|
||||
if (duplicate) {
|
||||
throw new Error(`Duplicate package documentation group "${duplicate.key}".`)
|
||||
}
|
||||
|
||||
return groups
|
||||
}
|
||||
|
||||
function getPackageGroupDefinition(key: string) {
|
||||
const group = packageGroupsByKey.get(key)
|
||||
if (!group) {
|
||||
throw new Error(`Unknown package documentation group "${key}".`)
|
||||
}
|
||||
return group
|
||||
}
|
||||
|
||||
function parsePackageDefinition(
|
||||
path: string,
|
||||
value: unknown
|
||||
@@ -236,6 +344,7 @@ function parsePackageDefinition(
|
||||
}
|
||||
|
||||
return {
|
||||
group: readMetaString(value, "group", path),
|
||||
key: match[1]!,
|
||||
label: readMetaString(value, "label", path),
|
||||
description: {
|
||||
|
||||
@@ -43,6 +43,7 @@ export type DocSearchEntry = {
|
||||
}
|
||||
|
||||
export type DocPackage = {
|
||||
group: string
|
||||
key: string
|
||||
label: string
|
||||
description: string
|
||||
@@ -50,6 +51,12 @@ export type DocPackage = {
|
||||
pages: readonly DocPage[]
|
||||
}
|
||||
|
||||
export type DocPackageGroup = {
|
||||
key: string
|
||||
label: string
|
||||
packages: readonly DocPackage[]
|
||||
}
|
||||
|
||||
export type DocLocation = {
|
||||
locale: DocLocale
|
||||
package: DocPackage
|
||||
|
||||
+42
-19
@@ -4,13 +4,18 @@ import {
|
||||
SiteFooter,
|
||||
SiteHeader,
|
||||
} from "../components/layout"
|
||||
import { getDocHref, getDocPackages } from "../content/registry"
|
||||
import { getDocHref, getDocPackageGroups } from "../content/registry"
|
||||
import { useLocale, useTranslate } from "../lib/locale"
|
||||
|
||||
export default function IndexPage() {
|
||||
const locale = useLocale()
|
||||
const t = useTranslate()
|
||||
const docPackages = getDocPackages(locale)
|
||||
const packageGroups = getDocPackageGroups(locale)
|
||||
const packageNumbers = new Map(
|
||||
packageGroups
|
||||
.flatMap((group) => group.packages)
|
||||
.map((docPackage, index) => [docPackage.key, index + 1])
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -19,27 +24,45 @@ export default function IndexPage() {
|
||||
actions={<Actions />}
|
||||
/>
|
||||
<Container
|
||||
className="grid grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3 lg:gap-8"
|
||||
className="space-y-12 lg:space-y-16"
|
||||
aria-label={t({ zh: "文档导航", us: "Documentation" })}
|
||||
>
|
||||
{docPackages.map((docPackage, index) => (
|
||||
<a
|
||||
key={docPackage.key}
|
||||
href={getDocHref(docPackage)}
|
||||
className="block overflow-hidden rounded-xl border border-slate-200 bg-indigo-50 outline-0 outline-offset-2 transition-colors hover:border-indigo-500 hover:bg-indigo-50 focus-visible:outline-2 focus-visible:outline-indigo-500 dark:border-slate-800 dark:bg-slate-900 dark:hover:border-indigo-400 dark:hover:bg-slate-800/80 dark:focus-visible:outline-indigo-400"
|
||||
{packageGroups.map((group) => (
|
||||
<section
|
||||
key={group.key}
|
||||
aria-labelledby={`package-group-${group.key}`}
|
||||
>
|
||||
<div className="min-h-fit p-4 sm:min-h-40 lg:min-h-46">
|
||||
<div className="text-slate-400 dark:text-slate-500">
|
||||
{index > 9 ? `${index + 1}` : `0${index + 1}`}
|
||||
</div>
|
||||
<h2 className="mt-2 mb-4 text-xl font-semibold text-slate-950 dark:text-slate-100">
|
||||
{docPackage.label}
|
||||
</h2>
|
||||
<p className="text-slate-700 dark:text-slate-300">
|
||||
{docPackage.description}
|
||||
</p>
|
||||
<h2
|
||||
id={`package-group-${group.key}`}
|
||||
className="mb-5 text-xl font-semibold text-slate-950 sm:mb-6 sm:text-2xl dark:text-slate-100"
|
||||
>
|
||||
{group.label}
|
||||
</h2>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3 lg:gap-8">
|
||||
{group.packages.map((docPackage) => (
|
||||
<a
|
||||
key={docPackage.key}
|
||||
href={getDocHref(docPackage)}
|
||||
className="block overflow-hidden rounded-xl border border-slate-200 bg-indigo-50 outline-0 outline-offset-2 transition-colors hover:border-indigo-500 hover:bg-indigo-50 focus-visible:outline-2 focus-visible:outline-indigo-500 dark:border-slate-800 dark:bg-slate-900 dark:hover:border-indigo-400 dark:hover:bg-slate-800/80 dark:focus-visible:outline-indigo-400"
|
||||
>
|
||||
<div className="min-h-fit p-4 sm:min-h-40 lg:min-h-46">
|
||||
<div className="text-slate-400 dark:text-slate-500">
|
||||
{String(packageNumbers.get(docPackage.key)).padStart(
|
||||
2,
|
||||
"0"
|
||||
)}
|
||||
</div>
|
||||
<h3 className="mt-2 mb-4 text-xl font-semibold text-slate-950 dark:text-slate-100">
|
||||
{docPackage.label}
|
||||
</h3>
|
||||
<p className="text-slate-700 dark:text-slate-300">
|
||||
{docPackage.description}
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</a>
|
||||
</section>
|
||||
))}
|
||||
</Container>
|
||||
<SiteFooter className="sm:py-12" />
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ function loadYamlModules(): Plugin {
|
||||
name: "load-yaml-modules",
|
||||
enforce: "pre",
|
||||
transform(source, id) {
|
||||
if (!/\/meta\.ya?ml(?:\?|$)/.test(id)) return
|
||||
if (!/\.ya?ml(?:\?|$)/.test(id)) return
|
||||
|
||||
try {
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
group: foundations
|
||||
order: 20
|
||||
label: Appearance
|
||||
description:
|
||||
zh-Hans: 使用原子化控件、主题词典和可组合偏好副作用构建应用外观设置。
|
||||
en-US: Build application appearance settings with atomic controls, theme tokens, and composable preference effects.
|
||||
zh-Hans: 配置主题、颜色、文字缩放、动态效果与界面亮度。
|
||||
en-US: Configure themes, colors, text scaling, motion, and interface brightness.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
group: application
|
||||
order: 10
|
||||
label: Blocks
|
||||
description:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
order: 30
|
||||
group: content
|
||||
order: 40
|
||||
label: Cropper
|
||||
description:
|
||||
zh-Hans: 通过可组合交互、几何计算和 Canvas 导出构建图片裁剪流程。
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
group: foundations
|
||||
order: 40
|
||||
label: I18n
|
||||
description:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
order: 50
|
||||
group: content
|
||||
order: 20
|
||||
label: Lexical
|
||||
description:
|
||||
zh-Hans: 从根节点和内容区域开始,逐步构建支持自定义操作与媒体的富文本编辑器。
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
order: 60
|
||||
group: content
|
||||
order: 30
|
||||
label: Media
|
||||
description:
|
||||
zh-Hans: 通过存储 Adapter 构建媒体浏览、上传、目录管理和资源选择流程。
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
order: 70
|
||||
group: application
|
||||
order: 30
|
||||
label: Overlays
|
||||
description:
|
||||
zh-Hans: 使用统一的异步 API 编排 alert、confirm、prompt、modal、sheet、drawer 和 toast。
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
order: 80
|
||||
group: application
|
||||
order: 20
|
||||
label: Page
|
||||
description:
|
||||
zh-Hans: 组合页面标题、描述、操作、面包屑、路由标签和内容区,并按需将标题挂载到外部布局。
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
order: 90
|
||||
group: foundations
|
||||
order: 30
|
||||
label: Preferences
|
||||
description:
|
||||
zh-Hans: 使用 SSR 快照、持久化回调和可组合副作用管理类型安全且可扩展的应用偏好。
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
order: 100
|
||||
group: content
|
||||
order: 10
|
||||
label: Record
|
||||
description:
|
||||
zh-Hans: 通过可组合上下文、类型安全字段路径和记录迭代器构建数据驱动界面。
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
order: 110
|
||||
group: application
|
||||
order: 40
|
||||
label: Search
|
||||
description:
|
||||
zh-Hans: 通过可替换的 Adapter、分组结果、分页和搜索历史构建应用级搜索体验。
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
order: 120
|
||||
group: foundations
|
||||
order: 10
|
||||
label: UI
|
||||
description:
|
||||
zh-Hans: 配置共享 UI 层,并学习组合组件、Hooks、图标和本地化词典。
|
||||
|
||||
Reference in New Issue
Block a user