@ciderpress/ui 1.0.0-rc.1 → 1.0.0-rc.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/dist/head/css/loader-apple.css +1 -1
  2. package/dist/head/css/loader-dots.css +1 -1
  3. package/dist/head/css/themes/amber.css +1 -1
  4. package/dist/head/css/themes/arcade.css +1 -1
  5. package/dist/head/css/themes/grannysmith.css +1 -1
  6. package/dist/head/css/themes/honeycrisp.css +1 -1
  7. package/dist/head/css/themes/midnight.css +1 -1
  8. package/dist/head/css/themes/mulled.css +1 -1
  9. package/dist/index.d.ts +1 -1
  10. package/dist/index.mjs +4 -3
  11. package/dist/node.d.ts +1 -1
  12. package/dist/node.mjs +732 -55
  13. package/dist/plugins/mermaid/MermaidRenderer.tsx +49 -6
  14. package/dist/theme/components/footer/site-footer.tsx +68 -9
  15. package/dist/theme/components/home/cta.css +15 -0
  16. package/dist/theme/components/home/cta.tsx +10 -2
  17. package/dist/theme/components/home/feature-card.css +1 -1
  18. package/dist/theme/components/home/feature-card.tsx +8 -10
  19. package/dist/theme/components/home/feature.tsx +54 -31
  20. package/dist/theme/components/home/hero-demo.css +38 -0
  21. package/dist/theme/components/home/home-visual.tsx +226 -0
  22. package/dist/theme/components/home/layout.tsx +410 -108
  23. package/dist/theme/components/home/split.css +46 -19
  24. package/dist/theme/components/home/split.tsx +28 -8
  25. package/dist/theme/components/home/tabs.css +299 -0
  26. package/dist/theme/components/home/tabs.tsx +351 -0
  27. package/dist/theme/components/home/trust-strip.css +41 -0
  28. package/dist/theme/components/home/trust-strip.tsx +168 -11
  29. package/dist/theme/components/home/workspaces.tsx +176 -38
  30. package/dist/theme/components/nav/branch-tag.tsx +7 -7
  31. package/dist/theme/components/nav/ciderpress-docs-bar.css +13 -1
  32. package/dist/theme/components/nav/ciderpress-docs-bar.tsx +33 -2
  33. package/dist/theme/components/nav/ciderpress-header.css +2 -1
  34. package/dist/theme/components/nav/ciderpress-header.tsx +26 -3
  35. package/dist/theme/components/nav/ciderpress-nav-hamburger.css +50 -0
  36. package/dist/theme/components/nav/ciderpress-nav-hamburger.tsx +95 -9
  37. package/dist/theme/components/nav/ciderpress-nav-menu.css +88 -0
  38. package/dist/theme/components/nav/ciderpress-nav-menu.tsx +397 -38
  39. package/dist/theme/components/nav/ciderpress-nav-social-links.tsx +27 -22
  40. package/dist/theme/components/nav/floating-branch-indicator.tsx +7 -7
  41. package/dist/theme/components/nav/header-icon.css +24 -0
  42. package/dist/theme/components/nav/header-icon.tsx +119 -0
  43. package/dist/theme/components/nav/header-logo.css +16 -0
  44. package/dist/theme/components/nav/header-logo.tsx +200 -0
  45. package/dist/theme/components/nav/layout.tsx +117 -16
  46. package/dist/theme/components/nav/nav-logo.tsx +16 -19
  47. package/dist/theme/components/openapi/index.ts +0 -2
  48. package/dist/theme/components/openapi/openapi.css +0 -14
  49. package/dist/theme/components/shared/card-icon.tsx +37 -0
  50. package/dist/theme/components/shared/ciderpress-logo.tsx +5 -1
  51. package/dist/theme/components/shared/icon.tsx +120 -35
  52. package/dist/theme/components/shared/resolve-card-icon.ts +53 -12
  53. package/dist/theme/components/shared/section-card.tsx +37 -6
  54. package/dist/theme/components/sidebar/sidebar-badge.css +50 -0
  55. package/dist/theme/components/sidebar/sidebar-badge.tsx +153 -0
  56. package/dist/theme/components/sidebar/sidebar-links.tsx +6 -2
  57. package/dist/theme/components/sidebar/sidebar-scope.tsx +24 -1
  58. package/dist/theme/components/theme-provider.tsx +27 -11
  59. package/dist/theme/components/workspaces/card.css +4 -0
  60. package/dist/theme/components/workspaces/card.tsx +10 -12
  61. package/dist/theme/components/workspaces/grid.tsx +18 -6
  62. package/dist/theme/hooks/use-ciderpress.ts +65 -5
  63. package/dist/theme/hooks/use-nav-items.ts +82 -11
  64. package/dist/theme/index.tsx +10 -3
  65. package/dist/theme/lib/read-social-links.ts +4 -3
  66. package/dist/theme/lib/rich-text-parse.ts +740 -0
  67. package/dist/theme/lib/rich-text.test.tsx +342 -0
  68. package/dist/theme/lib/rich-text.tsx +86 -0
  69. package/dist/theme/lib/theme-favicon.ts +1 -1
  70. package/dist/theme/lib/with-mount-base.ts +31 -0
  71. package/dist/theme/styles/overrides/fonts.css +7 -4
  72. package/dist/theme/styles/overrides/footnotes.css +54 -0
  73. package/dist/theme/styles/overrides/home.css +11 -2
  74. package/dist/theme/styles/overrides/lists.css +83 -0
  75. package/dist/theme/styles/overrides/rail.css +27 -10
  76. package/dist/theme/styles/overrides/rspress.css +10 -5
  77. package/dist/theme/styles/overrides/section-card.css +9 -0
  78. package/dist/theme/styles/overrides/sidebar.css +22 -0
  79. package/dist/theme/styles/overrides/tokens.css +41 -23
  80. package/dist/theme/styles/rich-text.css +106 -0
  81. package/dist/theme/styles/themes/amber.css +32 -2
  82. package/dist/theme/styles/themes/arcade.css +16 -1
  83. package/dist/theme/styles/themes/grannysmith.css +32 -2
  84. package/dist/theme/styles/themes/honeycrisp.css +37 -7
  85. package/dist/theme/styles/themes/midnight.css +16 -1
  86. package/dist/theme/styles/themes/mulled.css +54 -9
  87. package/package.json +18 -16
  88. package/src/theme/components/footer/site-footer.tsx +68 -9
  89. package/src/theme/components/home/cta.css +15 -0
  90. package/src/theme/components/home/cta.tsx +10 -2
  91. package/src/theme/components/home/feature-card.css +1 -1
  92. package/src/theme/components/home/feature-card.tsx +8 -10
  93. package/src/theme/components/home/feature.tsx +54 -31
  94. package/src/theme/components/home/hero-demo.css +38 -0
  95. package/src/theme/components/home/home-visual.tsx +226 -0
  96. package/src/theme/components/home/layout.tsx +410 -108
  97. package/src/theme/components/home/split.css +46 -19
  98. package/src/theme/components/home/split.tsx +28 -8
  99. package/src/theme/components/home/tabs.css +299 -0
  100. package/src/theme/components/home/tabs.tsx +351 -0
  101. package/src/theme/components/home/trust-strip.css +41 -0
  102. package/src/theme/components/home/trust-strip.tsx +168 -11
  103. package/src/theme/components/home/workspaces.tsx +176 -38
  104. package/src/theme/components/nav/branch-tag.tsx +7 -7
  105. package/src/theme/components/nav/ciderpress-docs-bar.css +13 -1
  106. package/src/theme/components/nav/ciderpress-docs-bar.tsx +33 -2
  107. package/src/theme/components/nav/ciderpress-header.css +2 -1
  108. package/src/theme/components/nav/ciderpress-header.tsx +26 -3
  109. package/src/theme/components/nav/ciderpress-nav-hamburger.css +50 -0
  110. package/src/theme/components/nav/ciderpress-nav-hamburger.tsx +95 -9
  111. package/src/theme/components/nav/ciderpress-nav-menu.css +88 -0
  112. package/src/theme/components/nav/ciderpress-nav-menu.tsx +397 -38
  113. package/src/theme/components/nav/ciderpress-nav-social-links.tsx +27 -22
  114. package/src/theme/components/nav/floating-branch-indicator.tsx +7 -7
  115. package/src/theme/components/nav/header-icon.css +24 -0
  116. package/src/theme/components/nav/header-icon.tsx +119 -0
  117. package/src/theme/components/nav/header-logo.css +16 -0
  118. package/src/theme/components/nav/header-logo.tsx +200 -0
  119. package/src/theme/components/nav/layout.tsx +117 -16
  120. package/src/theme/components/nav/nav-logo.tsx +16 -19
  121. package/src/theme/components/openapi/index.ts +0 -2
  122. package/src/theme/components/openapi/openapi.css +0 -14
  123. package/src/theme/components/shared/card-icon.tsx +37 -0
  124. package/src/theme/components/shared/ciderpress-logo.tsx +5 -1
  125. package/src/theme/components/shared/icon.tsx +120 -35
  126. package/src/theme/components/shared/resolve-card-icon.ts +53 -12
  127. package/src/theme/components/shared/section-card.tsx +37 -6
  128. package/src/theme/components/sidebar/sidebar-badge.css +50 -0
  129. package/src/theme/components/sidebar/sidebar-badge.tsx +153 -0
  130. package/src/theme/components/sidebar/sidebar-links.tsx +6 -2
  131. package/src/theme/components/sidebar/sidebar-scope.tsx +24 -1
  132. package/src/theme/components/theme-provider.tsx +27 -11
  133. package/src/theme/components/workspaces/card.css +4 -0
  134. package/src/theme/components/workspaces/card.tsx +10 -12
  135. package/src/theme/components/workspaces/grid.tsx +18 -6
  136. package/src/theme/hooks/use-ciderpress.ts +65 -5
  137. package/src/theme/hooks/use-nav-items.ts +82 -11
  138. package/src/theme/index.tsx +10 -3
  139. package/src/theme/lib/read-social-links.ts +4 -3
  140. package/src/theme/lib/rich-text-parse.ts +740 -0
  141. package/src/theme/lib/rich-text.test.tsx +342 -0
  142. package/src/theme/lib/rich-text.tsx +86 -0
  143. package/src/theme/lib/theme-favicon.ts +1 -1
  144. package/src/theme/lib/with-mount-base.ts +31 -0
  145. package/src/theme/styles/overrides/fonts.css +7 -4
  146. package/src/theme/styles/overrides/footnotes.css +54 -0
  147. package/src/theme/styles/overrides/home.css +11 -2
  148. package/src/theme/styles/overrides/lists.css +83 -0
  149. package/src/theme/styles/overrides/rail.css +27 -10
  150. package/src/theme/styles/overrides/rspress.css +10 -5
  151. package/src/theme/styles/overrides/section-card.css +9 -0
  152. package/src/theme/styles/overrides/sidebar.css +22 -0
  153. package/src/theme/styles/overrides/tokens.css +41 -23
  154. package/src/theme/styles/rich-text.css +106 -0
  155. package/src/theme/styles/themes/amber.css +32 -2
  156. package/src/theme/styles/themes/arcade.css +16 -1
  157. package/src/theme/styles/themes/grannysmith.css +32 -2
  158. package/src/theme/styles/themes/honeycrisp.css +37 -7
  159. package/src/theme/styles/themes/midnight.css +16 -1
  160. package/src/theme/styles/themes/mulled.css +54 -9
  161. package/dist/theme/components/openapi/copy-markdown-button.tsx +0 -41
  162. package/src/theme/components/openapi/copy-markdown-button.tsx +0 -41
@@ -11,6 +11,8 @@ declare const __CIDERPRESS_DEFAULT_VARIANT__: string
11
11
  declare const __CIDERPRESS_THEME_COLORS__: string
12
12
  declare const __CIDERPRESS_THEME_DARK_COLORS__: string
13
13
  declare const __CIDERPRESS_THEME_REGISTRY__: string
14
+ declare const __CIDERPRESS_HAS_USER_FAVICON__: boolean
15
+ declare const __CIDERPRESS_LOADER_MIN_MS__: number
14
16
 
15
17
  interface RegistryEntry {
16
18
  readonly name: string
@@ -26,7 +28,7 @@ type ThemeColorKey = keyof ThemeColors
26
28
 
27
29
  /**
28
30
  * Parsed theme registry — built-in themes plus any user themes from
29
- * `config.themes`. Read from the build-time define so the client bundle
31
+ * `theme.themes`. Read from the build-time define so the client bundle
30
32
  * does not pull `@ciderpress/theme`'s factory + Zod into the runtime path.
31
33
  */
32
34
  const REGISTRY_ENTRIES: readonly RegistryEntry[] = parseRegistry(__CIDERPRESS_THEME_REGISTRY__)
@@ -123,9 +125,11 @@ const COLOR_VAR_MAP: Readonly<Record<ThemeColorKey, readonly string[]>> = Object
123
125
  const ALL_CSS_VARS: readonly string[] = Object.values(COLOR_VAR_MAP).flat()
124
126
 
125
127
  /**
126
- * Minimum time (ms) the loading overlay stays visible before fading out.
128
+ * Minimum time (ms) the loading overlay stays visible before fading
129
+ * out. Resolved at build time from `brand.loader.minDisplayMs` —
130
+ * defaults to 150ms in `packages/ui/src/config.ts`.
127
131
  */
128
- const LOADER_MIN_DISPLAY_MS = 150
132
+ const LOADER_MIN_DISPLAY_MS = __CIDERPRESS_LOADER_MIN_MS__
129
133
 
130
134
  /**
131
135
  * Duration (ms) of the CSS fade-out transition. Must match the
@@ -199,12 +203,14 @@ export function ThemeProvider(): React.ReactElement | null {
199
203
  applyColorOverrides(html, darkColors)
200
204
  }
201
205
 
202
- // Sync the document favicon with the active theme's brand colour.
203
- // Browsers cache static icon assets and ignore CSS, so the only way to
204
- // keep the tab mark in lockstep with the chosen theme is to swap
205
- // `<link rel="icon">` to a data-URI SVG carrying the resolved
206
- // `--cp-c-brand-1`.
207
- syncThemeFavicon(html)
206
+ // Sync the document favicon with the active theme's brand colour —
207
+ // only when the user has NOT set their own `favicon` in config. When
208
+ // they have, Rspress already points `<link rel="icon">` at the
209
+ // user's asset, and overwriting it with the themed apple data-URI
210
+ // would silently restore ciderpress branding.
211
+ if (!__CIDERPRESS_HAS_USER_FAVICON__) {
212
+ syncThemeFavicon(html)
213
+ }
208
214
 
209
215
  // Observe `.rp-dark` class changes so Rspress's built-in dark toggle
210
216
  // stays the single source of truth for variant flips. The new variant
@@ -253,8 +259,11 @@ export function ThemeProvider(): React.ReactElement | null {
253
259
  }
254
260
  // Re-sync the favicon — brand colour is constant across variants for
255
261
  // built-in themes, but surface overrides on the dark variant can
256
- // affect the chip background colour we bake into the SVG.
257
- syncThemeFavicon(html)
262
+ // affect the chip background colour we bake into the SVG. Skipped
263
+ // when the user supplied their own favicon (see initial sync above).
264
+ if (!__CIDERPRESS_HAS_USER_FAVICON__) {
265
+ syncThemeFavicon(html)
266
+ }
258
267
  })
259
268
  observer.observe(html, { attributes: true, attributeFilter: ['class'] })
260
269
 
@@ -628,6 +637,13 @@ function dismissLoader(html: HTMLElement): () => void {
628
637
  clearDotsInterval()
629
638
  }, LOADER_MIN_DISPLAY_MS + LOADER_FADE_MS)
630
639
 
640
+ // The forced-dismiss fallback (covering the case where React never
641
+ // hydrates) lives in the inline head script — see `buildHeadScriptBody`
642
+ // in `packages/ui/src/config.ts`. That timer always runs, so a
643
+ // duplicate React-side fallback here would only fire AFTER ThemeProvider
644
+ // has already mounted — at which point the normal fade path above has
645
+ // already won.
646
+
631
647
  return () => {
632
648
  clearTimeout(fadeTimer)
633
649
  clearTimeout(removeTimer)
@@ -69,6 +69,10 @@
69
69
  font-size: 15px;
70
70
  font-weight: 700;
71
71
  color: var(--cp-c-text-1);
72
+ /* Presentational lowercase. Done here rather than in JS because the
73
+ title may carry inline markup — lowercasing the source would also
74
+ lowercase link targets and class names inside it. */
75
+ text-transform: lowercase;
72
76
  }
73
77
 
74
78
  .cp-workspace-card__desc {
@@ -1,8 +1,10 @@
1
1
  import { match, P } from 'massaman/match'
2
2
  import type React from 'react'
3
3
 
4
+ import { renderRichText } from '../../lib/rich-text.tsx'
4
5
  import { Card } from '../shared/card'
5
- import { Icon } from '../shared/icon'
6
+ import { CardIcon } from '../shared/card-icon'
7
+ import type { CardIconInput } from '../shared/resolve-card-icon'
6
8
  import { resolveCardIcon } from '../shared/resolve-card-icon'
7
9
  import { TechTag } from '../shared/tech-tag'
8
10
 
@@ -16,9 +18,10 @@ export interface WorkspaceCardProps {
16
18
  */
17
19
  readonly href: string
18
20
  /**
19
- * Icon config — Iconify identifier string or `{ id, color }` object.
21
+ * Icon config — Iconify identifier string, `{ id, color }` object, or
22
+ * `{ kind: 'image', src, alt }` for a static image asset.
20
23
  */
21
- readonly icon?: string | { readonly id: string; readonly color: string }
24
+ readonly icon?: CardIconInput
22
25
  /**
23
26
  * Scope prefix shown above the name (e.g. "apps/").
24
27
  */
@@ -63,17 +66,12 @@ export function WorkspaceCard({
63
66
  titleLines,
64
67
  descriptionLines,
65
68
  }: WorkspaceCardProps): React.ReactElement {
66
- const name = title.toLowerCase()
67
69
  const resolved = resolveCardIcon(icon)
68
70
 
69
71
  const iconEl = match(resolved)
70
- .with(P.nonNullable, (r) => <Icon icon={r.id} />)
72
+ .with(P.nonNullable, (r) => <CardIcon resolved={r} className="cp-card__icon" />)
71
73
  .otherwise(() => null)
72
74
 
73
- const iconColor = match(resolved)
74
- .with(P.nonNullable, (r) => r.color)
75
- .otherwise(() => 'purple')
76
-
77
75
  const scopeEl = match(scope)
78
76
  .with(
79
77
  P.when((s): s is string => s !== undefined && s.length > 0),
@@ -95,7 +93,7 @@ export function WorkspaceCard({
95
93
  className={clampClass('cp-workspace-card__desc', descriptionLines)}
96
94
  style={clampStyle(descriptionLines)}
97
95
  >
98
- {d}
96
+ {renderRichText(d)}
99
97
  </span>
100
98
  ))
101
99
  .otherwise(() => null)
@@ -117,14 +115,14 @@ export function WorkspaceCard({
117
115
  <Card href={href} className="cp-workspace-card">
118
116
  <div className="cp-workspace-card__header">
119
117
  <div className="cp-workspace-card__identity">
120
- <span className={`cp-card__icon cp-card__icon--${iconColor}`}>{iconEl}</span>
118
+ {iconEl}
121
119
  <div className="cp-workspace-card__title">
122
120
  {scopeEl}
123
121
  <span
124
122
  className={clampClass('cp-workspace-card__name', titleLines)}
125
123
  style={clampStyle(titleLines)}
126
124
  >
127
- {name}
125
+ {renderRichText(title)}
128
126
  </span>
129
127
  </div>
130
128
  </div>
@@ -1,16 +1,21 @@
1
+ import { match, P } from 'massaman/match'
1
2
  import type React from 'react'
2
3
 
4
+ import { renderRichText } from '../../lib/rich-text.tsx'
5
+
3
6
  import './card.css'
4
7
 
5
8
  export interface WorkspaceGridProps {
6
9
  /**
7
- * Section heading (e.g. "Apps", "Packages").
10
+ * Section heading (e.g. "Apps", "Packages"). Omitted for an ungrouped
11
+ * grid, such as a showcase block with an explicit `source` list.
8
12
  */
9
- readonly heading: string
13
+ readonly heading?: string
10
14
  /**
11
- * Brief description rendered below the heading.
15
+ * Brief description rendered below the heading. Omitted for an
16
+ * ungrouped grid.
12
17
  */
13
- readonly description: string
18
+ readonly description?: string
14
19
  /**
15
20
  * Number of grid columns.
16
21
  */
@@ -38,8 +43,15 @@ export function WorkspaceGrid({
38
43
 
39
44
  return (
40
45
  <>
41
- <h2>{heading}</h2>
42
- <p className="cp-workspace-section__desc">{description}</p>
46
+ {/* Rich text, matching the card titles and descriptions this grid
47
+ wraps — a group heading was the one string in the block that
48
+ showed its `**markers**` literally. */}
49
+ {match(heading)
50
+ .with(P.string, (h) => <h2>{renderRichText(h)}</h2>)
51
+ .otherwise(() => null)}
52
+ {match(description)
53
+ .with(P.string, (d) => <p className="cp-workspace-section__desc">{renderRichText(d)}</p>)
54
+ .otherwise(() => null)}
43
55
  <div className="cp-workspace-grid" style={style}>
44
56
  {children}
45
57
  </div>
@@ -1,17 +1,17 @@
1
- import type { FooterConfig, HomeConfig, SiteConfig } from '@ciderpress/config'
1
+ import type { BadgeConfig, FooterConfig, HomeConfig, SerializedIcon } from '@ciderpress/config'
2
2
  import { useSite } from '@rspress/core/runtime'
3
3
 
4
4
  export interface CiderpressSidebarItem {
5
5
  readonly text?: string
6
6
  readonly link?: string
7
- readonly icon?: string
7
+ readonly icon?: SerializedIcon
8
8
  readonly items?: readonly CiderpressSidebarItem[]
9
9
  }
10
10
 
11
11
  export interface CiderpressSidebarLink {
12
12
  readonly text: string
13
13
  readonly link: string
14
- readonly icon?: string | { readonly id: string; readonly color: string }
14
+ readonly icon?: SerializedIcon
15
15
  readonly style?: 'brand' | 'alt' | 'ghost'
16
16
  readonly shape?: 'square' | 'rounded' | 'circle'
17
17
  }
@@ -19,7 +19,7 @@ export interface CiderpressSidebarLink {
19
19
  export interface WorkspaceCardData {
20
20
  readonly title: string
21
21
  readonly href: string
22
- readonly icon: string | { readonly id: string; readonly color: string } | undefined
22
+ readonly icon: SerializedIcon | undefined
23
23
  readonly scope: string | undefined
24
24
  readonly description: string | undefined
25
25
  readonly tags: readonly string[]
@@ -33,6 +33,65 @@ export interface WorkspaceGroupData {
33
33
  readonly cards: readonly WorkspaceCardData[]
34
34
  }
35
35
 
36
+ /**
37
+ * Serialised view of the unified `CiderpressConfig` that the runtime
38
+ * theme components still read through. The `site` field is rebuilt in
39
+ * `packages/ui/src/config.ts` (`buildSiteBlock`) from the new top-level
40
+ * `editLink` / `reportLink` / `topbar` / `sidebar` / `footer` / `version`
41
+ * config — kept under the legacy `site.*` name so theme components don't
42
+ * need to be rewired to look up each field individually.
43
+ */
44
+ export interface CiderpressSiteBlock {
45
+ readonly version: string | undefined
46
+ readonly edit:
47
+ | {
48
+ readonly repo: string
49
+ readonly branch?: string
50
+ readonly directory?: string
51
+ readonly label?: string
52
+ }
53
+ | undefined
54
+ readonly report:
55
+ | {
56
+ readonly repo: string
57
+ readonly branch?: string
58
+ readonly directory?: string
59
+ readonly label?: string
60
+ }
61
+ | undefined
62
+ readonly topbarCta: { readonly text: string; readonly href: string } | undefined
63
+ readonly sidebarPromo:
64
+ | {
65
+ readonly title: string
66
+ readonly body: string
67
+ readonly cta: { readonly text: string; readonly href: string }
68
+ }
69
+ | undefined
70
+ readonly announcement:
71
+ | {
72
+ readonly id?: string
73
+ readonly lead?: string
74
+ readonly message: string
75
+ readonly cta?: { readonly href: string; readonly label: string }
76
+ readonly persistent?: boolean
77
+ }
78
+ | undefined
79
+ readonly footer:
80
+ | {
81
+ readonly columns?: readonly {
82
+ readonly heading: string
83
+ readonly links: readonly { readonly text: string; readonly href: string }[]
84
+ }[]
85
+ readonly tagline?: string
86
+ readonly brandMark?: string
87
+ }
88
+ | undefined
89
+ readonly feedback: {
90
+ readonly enabled: boolean
91
+ readonly question: string | undefined
92
+ }
93
+ }
94
+
36
95
  interface CiderpressThemeConfig {
37
96
  readonly sidebar: Record<string, readonly CiderpressSidebarItem[]>
38
97
  readonly sidebarAbove: readonly CiderpressSidebarLink[] | undefined
@@ -41,7 +100,8 @@ interface CiderpressThemeConfig {
41
100
  readonly standaloneScopePaths: readonly string[] | undefined
42
101
  readonly home: HomeConfig | undefined
43
102
  readonly ciderpressFooter: FooterConfig | undefined
44
- readonly site: SiteConfig | undefined
103
+ readonly site: CiderpressSiteBlock | undefined
104
+ readonly pageBadges: Record<string, readonly BadgeConfig[]> | undefined
45
105
  }
46
106
 
47
107
  /**
@@ -1,3 +1,4 @@
1
+ import { removeBase } from '@rspress/core/runtime'
1
2
  import { useEffect, useState } from 'react'
2
3
 
3
4
  import type { CiderpressNavMenuItem } from '../components/nav/ciderpress-nav-menu'
@@ -41,34 +42,104 @@ export function useNavItems(): readonly CiderpressNavMenuItem[] {
41
42
  }
42
43
 
43
44
  /**
44
- * Read every anchor under Rspress's hidden `.rp-nav-menu` and project
45
- * it into a `{ text, link }` item. Anchors with empty text or `href`
46
- * are dropped so we never surface a placeholder entry.
45
+ * Reconstruct the primary nav from Rspress's hidden `.rp-nav-menu`,
46
+ * preserving dropdowns. Each top-level `.rp-nav-menu__item` is either a
47
+ * leaf (its container is an anchor) or a dropdown parent (it wraps a
48
+ * `.rp-hover-group` of child links). Items with empty text, or dropdown
49
+ * parents with no usable children, are dropped.
47
50
  *
48
51
  * @private
49
52
  * @returns Nav items currently in the DOM (empty array when not mounted).
50
53
  */
51
54
  function scrapeNavItems(): readonly CiderpressNavMenuItem[] {
52
- const anchors = document.querySelectorAll<HTMLAnchorElement>('.rp-nav-menu .rp-nav-menu__item a')
55
+ return navMenuRoots()
56
+ .map(scrapeNavItem)
57
+ .filter((item): item is CiderpressNavMenuItem => item !== null)
58
+ }
59
+
60
+ /**
61
+ * Collect the top-level `.rp-nav-menu__item` `<li>`s to scrape. Rspress
62
+ * renders separate left and right nav `<ul>`s; the ciderpress topbar is
63
+ * right-aligned, so we read the right menu and only fall back to the
64
+ * unscoped selector when it isn't present.
65
+ *
66
+ * @private
67
+ * @returns Top-level nav item elements.
68
+ */
69
+ function navMenuRoots(): readonly HTMLElement[] {
70
+ const right = document.querySelectorAll<HTMLElement>('.rp-nav-menu--right > .rp-nav-menu__item')
71
+ if (right.length > 0) {
72
+ return [...right]
73
+ }
74
+ return [...document.querySelectorAll<HTMLElement>('.rp-nav-menu > .rp-nav-menu__item')]
75
+ }
76
+
77
+ /**
78
+ * Project a single top-level `.rp-nav-menu__item` element into a nav
79
+ * item, recursing one level into its `.rp-hover-group` dropdown when
80
+ * present.
81
+ *
82
+ * @private
83
+ * @param root - Top-level nav `<li>` element.
84
+ * @returns Parsed nav item, or `null` when unusable.
85
+ */
86
+ function scrapeNavItem(root: HTMLElement): CiderpressNavMenuItem | null {
87
+ const container = root.querySelector(':scope > .rp-nav-menu__item__container')
88
+ if (container === null) {
89
+ return null
90
+ }
91
+ const text = readElementText(container)
92
+ if (text === '') {
93
+ return null
94
+ }
95
+ const group = root.querySelector(':scope > .rp-hover-group')
96
+ if (group !== null) {
97
+ const items = scrapeGroupItems(group)
98
+ if (items.length === 0) {
99
+ return null
100
+ }
101
+ return { text, items }
102
+ }
103
+ const href = container.getAttribute('href')
104
+ if (href === null || href === '') {
105
+ return null
106
+ }
107
+ // Un-base the scraped href so `<Link>` re-applies the site `base` once
108
+ // rather than doubling the mount prefix on subpath deploys.
109
+ return { text, link: removeBase(href) }
110
+ }
111
+
112
+ /**
113
+ * Read the child links out of a Rspress `.rp-hover-group` dropdown.
114
+ *
115
+ * @private
116
+ * @param group - The `.rp-hover-group` element.
117
+ * @returns Child nav items with text + link (empties dropped).
118
+ */
119
+ function scrapeGroupItems(group: Element): readonly CiderpressNavMenuItem[] {
120
+ const anchors = group.querySelectorAll<HTMLAnchorElement>('.rp-hover-group__item__link')
53
121
  return [...anchors]
54
122
  .map((anchor) => ({
55
- text: readAnchorText(anchor),
56
- link: anchor.getAttribute('href') ?? '',
123
+ text: readElementText(anchor),
124
+ // Rspress's rendered hrefs already include the site `base`; strip it so
125
+ // the consuming `<Link>` re-applies it once instead of doubling the mount
126
+ // prefix on subpath deploys (the `/examples/<slug>/examples/<slug>/…` 404).
127
+ link: removeBase(anchor.getAttribute('href') ?? ''),
57
128
  }))
58
129
  .filter((item) => item.text !== '' && item.link !== '')
59
130
  }
60
131
 
61
132
  /**
62
- * Pull the trimmed text content from an anchor. Returns an empty
133
+ * Pull the trimmed text content from an element. Returns an empty
63
134
  * string when `textContent` is missing — callers treat empty as "skip
64
- * this anchor".
135
+ * this element".
65
136
  *
66
137
  * @private
67
- * @param anchor - Anchor element to read.
138
+ * @param element - Element to read.
68
139
  * @returns Trimmed inner text, or empty string when absent.
69
140
  */
70
- function readAnchorText(anchor: HTMLAnchorElement): string {
71
- const text = anchor.textContent
141
+ function readElementText(element: Element): string {
142
+ const text = element.textContent
72
143
  if (text === null) {
73
144
  return ''
74
145
  }
@@ -19,11 +19,11 @@ import '@rspress/core/dist/theme/components/Sidebar/SidebarDivider.css'
19
19
  import '@rspress/core/dist/theme/components/Sidebar/SidebarGroup.css'
20
20
  import '@rspress/core/dist/theme/components/Sidebar/SidebarItem.css'
21
21
  import '@rspress/core/dist/theme/components/Sidebar/SidebarSectionHeader.css'
22
-
23
22
  import './styles/layers.css'
24
23
  import './styles/overrides/fonts.css'
25
24
  import './styles/overrides/tokens.css'
26
25
  import './styles/overrides/rspress.css'
26
+ import './styles/rich-text.css'
27
27
  // Theme color palettes — scoped via [data-cp-theme][data-cp-variant] selectors.
28
28
  // `mulled.css` ships first because it is `DEFAULT_THEME_NAME` — its rules emit
29
29
  // the `:root { ... }` FOUC fallback that paints before JS hydrates the
@@ -39,6 +39,8 @@ import './styles/themes/arcade.css'
39
39
  // (border trace, neon pulse, CRT scanlines, etc.) scoped to [data-cp-theme='arcade']
40
40
  import './styles/themes/arcade-fx.css'
41
41
  import './styles/overrides/details.css'
42
+ import './styles/overrides/lists.css'
43
+ import './styles/overrides/footnotes.css'
42
44
  import './styles/overrides/scrollbar.css'
43
45
  import './styles/overrides/rail.css'
44
46
  import './styles/overrides/sidebar.css'
@@ -54,6 +56,7 @@ import './components/content-footer/feedback.css'
54
56
  import './components/content-footer/meta-actions.css'
55
57
  import './components/content-footer/page-pager.css'
56
58
  import './components/sidebar/framework-picker.css'
59
+ import './components/sidebar/sidebar-badge.css'
57
60
  import './components/sidebar/sidebar-promo.css'
58
61
  import './components/sidebar/sidebar-toggle.css'
59
62
  import './components/home/page-rail.css'
@@ -90,6 +93,8 @@ export { HomeFeature } from './components/home/feature'
90
93
  export { HomeLayout } from './components/home/layout'
91
94
  /** @internal Sidebar override — multi-scope filtering for standalone sections */
92
95
  export { Sidebar } from './components/sidebar/sidebar-scope'
96
+ /** @internal Tag override — renders ciderpress sidebar badges, delegates other tags to Rspress */
97
+ export { Tag } from './components/sidebar/sidebar-badge'
93
98
 
94
99
  export { FeatureCard, FeatureGrid } from './components/home/feature-card'
95
100
  export type { FeatureCardProps, FeatureItem } from './components/home/feature-card'
@@ -102,6 +107,10 @@ export { TrustStrip } from './components/home/trust-strip'
102
107
  export type { TrustStripProps } from './components/home/trust-strip'
103
108
  export { HomeSplit } from './components/home/split'
104
109
  export type { SplitProps, SplitAction } from './components/home/split'
110
+ export { HomeTabs } from './components/home/tabs'
111
+ export { RichText, renderRichText } from './lib/rich-text.tsx'
112
+ export { hasAccentMarker, toPlainText } from './lib/rich-text-parse.ts'
113
+ export type { HomeTabsProps, HomeTabEntry, TabsAction } from './components/home/tabs'
105
114
  export { CTA } from './components/home/cta'
106
115
  export type { CTAProps } from './components/home/cta'
107
116
  export { WorkspaceCard } from './components/workspaces/card'
@@ -150,8 +159,6 @@ export type { StepsProps, StepProps } from './components/shared/steps'
150
159
  export { Field, FieldGroup } from './components/shared/field'
151
160
  export type { FieldProps, FieldGroupProps } from './components/shared/field'
152
161
 
153
- export { CopyMarkdownButton } from './components/openapi'
154
- export type { CopyMarkdownButtonProps } from './components/openapi'
155
162
  export { OpenAPIOperation } from './components/openapi'
156
163
  export type { OpenAPIOperationProps } from './components/openapi'
157
164
  export { OpenAPIOverview } from './components/openapi'
@@ -2,8 +2,9 @@ import type { CiderpressSocialLink } from '../components/nav/ciderpress-nav-soci
2
2
 
3
3
  /**
4
4
  * Pull social-link entries (GitHub, npm, etc) from Rspress's `site`
5
- * shape. Lives on `site.themeConfig.socialLinks` after Rspress
6
- * normalisation but isn't surfaced on the public typings.
5
+ * shape. Lives on `site.themeConfig.socialLinks` after the
6
+ * `packages/ui/src/config.ts` serialiser writes the unified `socials`
7
+ * config into the runtime theme config.
7
8
  *
8
9
  * @param site - Rspress site data (typically the result of `useSite().site`)
9
10
  * @returns Array of social-link entries (empty array when not configured)
@@ -22,6 +23,6 @@ export function readSocialLinks(site: unknown): readonly CiderpressSocialLink[]
22
23
  typeof item === 'object' &&
23
24
  item !== null &&
24
25
  typeof (item as { icon?: unknown }).icon === 'string' &&
25
- typeof (item as { content?: unknown }).content === 'string'
26
+ typeof (item as { url?: unknown }).url === 'string'
26
27
  )
27
28
  }