@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.
- package/dist/head/css/loader-apple.css +1 -1
- package/dist/head/css/loader-dots.css +1 -1
- package/dist/head/css/themes/amber.css +1 -1
- package/dist/head/css/themes/arcade.css +1 -1
- package/dist/head/css/themes/grannysmith.css +1 -1
- package/dist/head/css/themes/honeycrisp.css +1 -1
- package/dist/head/css/themes/midnight.css +1 -1
- package/dist/head/css/themes/mulled.css +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +4 -3
- package/dist/node.d.ts +1 -1
- package/dist/node.mjs +732 -55
- package/dist/plugins/mermaid/MermaidRenderer.tsx +49 -6
- package/dist/theme/components/footer/site-footer.tsx +68 -9
- package/dist/theme/components/home/cta.css +15 -0
- package/dist/theme/components/home/cta.tsx +10 -2
- package/dist/theme/components/home/feature-card.css +1 -1
- package/dist/theme/components/home/feature-card.tsx +8 -10
- package/dist/theme/components/home/feature.tsx +54 -31
- package/dist/theme/components/home/hero-demo.css +38 -0
- package/dist/theme/components/home/home-visual.tsx +226 -0
- package/dist/theme/components/home/layout.tsx +410 -108
- package/dist/theme/components/home/split.css +46 -19
- package/dist/theme/components/home/split.tsx +28 -8
- package/dist/theme/components/home/tabs.css +299 -0
- package/dist/theme/components/home/tabs.tsx +351 -0
- package/dist/theme/components/home/trust-strip.css +41 -0
- package/dist/theme/components/home/trust-strip.tsx +168 -11
- package/dist/theme/components/home/workspaces.tsx +176 -38
- package/dist/theme/components/nav/branch-tag.tsx +7 -7
- package/dist/theme/components/nav/ciderpress-docs-bar.css +13 -1
- package/dist/theme/components/nav/ciderpress-docs-bar.tsx +33 -2
- package/dist/theme/components/nav/ciderpress-header.css +2 -1
- package/dist/theme/components/nav/ciderpress-header.tsx +26 -3
- package/dist/theme/components/nav/ciderpress-nav-hamburger.css +50 -0
- package/dist/theme/components/nav/ciderpress-nav-hamburger.tsx +95 -9
- package/dist/theme/components/nav/ciderpress-nav-menu.css +88 -0
- package/dist/theme/components/nav/ciderpress-nav-menu.tsx +397 -38
- package/dist/theme/components/nav/ciderpress-nav-social-links.tsx +27 -22
- package/dist/theme/components/nav/floating-branch-indicator.tsx +7 -7
- package/dist/theme/components/nav/header-icon.css +24 -0
- package/dist/theme/components/nav/header-icon.tsx +119 -0
- package/dist/theme/components/nav/header-logo.css +16 -0
- package/dist/theme/components/nav/header-logo.tsx +200 -0
- package/dist/theme/components/nav/layout.tsx +117 -16
- package/dist/theme/components/nav/nav-logo.tsx +16 -19
- package/dist/theme/components/openapi/index.ts +0 -2
- package/dist/theme/components/openapi/openapi.css +0 -14
- package/dist/theme/components/shared/card-icon.tsx +37 -0
- package/dist/theme/components/shared/ciderpress-logo.tsx +5 -1
- package/dist/theme/components/shared/icon.tsx +120 -35
- package/dist/theme/components/shared/resolve-card-icon.ts +53 -12
- package/dist/theme/components/shared/section-card.tsx +37 -6
- package/dist/theme/components/sidebar/sidebar-badge.css +50 -0
- package/dist/theme/components/sidebar/sidebar-badge.tsx +153 -0
- package/dist/theme/components/sidebar/sidebar-links.tsx +6 -2
- package/dist/theme/components/sidebar/sidebar-scope.tsx +24 -1
- package/dist/theme/components/theme-provider.tsx +27 -11
- package/dist/theme/components/workspaces/card.css +4 -0
- package/dist/theme/components/workspaces/card.tsx +10 -12
- package/dist/theme/components/workspaces/grid.tsx +18 -6
- package/dist/theme/hooks/use-ciderpress.ts +65 -5
- package/dist/theme/hooks/use-nav-items.ts +82 -11
- package/dist/theme/index.tsx +10 -3
- package/dist/theme/lib/read-social-links.ts +4 -3
- package/dist/theme/lib/rich-text-parse.ts +740 -0
- package/dist/theme/lib/rich-text.test.tsx +342 -0
- package/dist/theme/lib/rich-text.tsx +86 -0
- package/dist/theme/lib/theme-favicon.ts +1 -1
- package/dist/theme/lib/with-mount-base.ts +31 -0
- package/dist/theme/styles/overrides/fonts.css +7 -4
- package/dist/theme/styles/overrides/footnotes.css +54 -0
- package/dist/theme/styles/overrides/home.css +11 -2
- package/dist/theme/styles/overrides/lists.css +83 -0
- package/dist/theme/styles/overrides/rail.css +27 -10
- package/dist/theme/styles/overrides/rspress.css +10 -5
- package/dist/theme/styles/overrides/section-card.css +9 -0
- package/dist/theme/styles/overrides/sidebar.css +22 -0
- package/dist/theme/styles/overrides/tokens.css +41 -23
- package/dist/theme/styles/rich-text.css +106 -0
- package/dist/theme/styles/themes/amber.css +32 -2
- package/dist/theme/styles/themes/arcade.css +16 -1
- package/dist/theme/styles/themes/grannysmith.css +32 -2
- package/dist/theme/styles/themes/honeycrisp.css +37 -7
- package/dist/theme/styles/themes/midnight.css +16 -1
- package/dist/theme/styles/themes/mulled.css +54 -9
- package/package.json +18 -16
- package/src/theme/components/footer/site-footer.tsx +68 -9
- package/src/theme/components/home/cta.css +15 -0
- package/src/theme/components/home/cta.tsx +10 -2
- package/src/theme/components/home/feature-card.css +1 -1
- package/src/theme/components/home/feature-card.tsx +8 -10
- package/src/theme/components/home/feature.tsx +54 -31
- package/src/theme/components/home/hero-demo.css +38 -0
- package/src/theme/components/home/home-visual.tsx +226 -0
- package/src/theme/components/home/layout.tsx +410 -108
- package/src/theme/components/home/split.css +46 -19
- package/src/theme/components/home/split.tsx +28 -8
- package/src/theme/components/home/tabs.css +299 -0
- package/src/theme/components/home/tabs.tsx +351 -0
- package/src/theme/components/home/trust-strip.css +41 -0
- package/src/theme/components/home/trust-strip.tsx +168 -11
- package/src/theme/components/home/workspaces.tsx +176 -38
- package/src/theme/components/nav/branch-tag.tsx +7 -7
- package/src/theme/components/nav/ciderpress-docs-bar.css +13 -1
- package/src/theme/components/nav/ciderpress-docs-bar.tsx +33 -2
- package/src/theme/components/nav/ciderpress-header.css +2 -1
- package/src/theme/components/nav/ciderpress-header.tsx +26 -3
- package/src/theme/components/nav/ciderpress-nav-hamburger.css +50 -0
- package/src/theme/components/nav/ciderpress-nav-hamburger.tsx +95 -9
- package/src/theme/components/nav/ciderpress-nav-menu.css +88 -0
- package/src/theme/components/nav/ciderpress-nav-menu.tsx +397 -38
- package/src/theme/components/nav/ciderpress-nav-social-links.tsx +27 -22
- package/src/theme/components/nav/floating-branch-indicator.tsx +7 -7
- package/src/theme/components/nav/header-icon.css +24 -0
- package/src/theme/components/nav/header-icon.tsx +119 -0
- package/src/theme/components/nav/header-logo.css +16 -0
- package/src/theme/components/nav/header-logo.tsx +200 -0
- package/src/theme/components/nav/layout.tsx +117 -16
- package/src/theme/components/nav/nav-logo.tsx +16 -19
- package/src/theme/components/openapi/index.ts +0 -2
- package/src/theme/components/openapi/openapi.css +0 -14
- package/src/theme/components/shared/card-icon.tsx +37 -0
- package/src/theme/components/shared/ciderpress-logo.tsx +5 -1
- package/src/theme/components/shared/icon.tsx +120 -35
- package/src/theme/components/shared/resolve-card-icon.ts +53 -12
- package/src/theme/components/shared/section-card.tsx +37 -6
- package/src/theme/components/sidebar/sidebar-badge.css +50 -0
- package/src/theme/components/sidebar/sidebar-badge.tsx +153 -0
- package/src/theme/components/sidebar/sidebar-links.tsx +6 -2
- package/src/theme/components/sidebar/sidebar-scope.tsx +24 -1
- package/src/theme/components/theme-provider.tsx +27 -11
- package/src/theme/components/workspaces/card.css +4 -0
- package/src/theme/components/workspaces/card.tsx +10 -12
- package/src/theme/components/workspaces/grid.tsx +18 -6
- package/src/theme/hooks/use-ciderpress.ts +65 -5
- package/src/theme/hooks/use-nav-items.ts +82 -11
- package/src/theme/index.tsx +10 -3
- package/src/theme/lib/read-social-links.ts +4 -3
- package/src/theme/lib/rich-text-parse.ts +740 -0
- package/src/theme/lib/rich-text.test.tsx +342 -0
- package/src/theme/lib/rich-text.tsx +86 -0
- package/src/theme/lib/theme-favicon.ts +1 -1
- package/src/theme/lib/with-mount-base.ts +31 -0
- package/src/theme/styles/overrides/fonts.css +7 -4
- package/src/theme/styles/overrides/footnotes.css +54 -0
- package/src/theme/styles/overrides/home.css +11 -2
- package/src/theme/styles/overrides/lists.css +83 -0
- package/src/theme/styles/overrides/rail.css +27 -10
- package/src/theme/styles/overrides/rspress.css +10 -5
- package/src/theme/styles/overrides/section-card.css +9 -0
- package/src/theme/styles/overrides/sidebar.css +22 -0
- package/src/theme/styles/overrides/tokens.css +41 -23
- package/src/theme/styles/rich-text.css +106 -0
- package/src/theme/styles/themes/amber.css +32 -2
- package/src/theme/styles/themes/arcade.css +16 -1
- package/src/theme/styles/themes/grannysmith.css +32 -2
- package/src/theme/styles/themes/honeycrisp.css +37 -7
- package/src/theme/styles/themes/midnight.css +16 -1
- package/src/theme/styles/themes/mulled.css +54 -9
- package/dist/theme/components/openapi/copy-markdown-button.tsx +0 -41
- package/src/theme/components/openapi/copy-markdown-button.tsx +0 -41
|
@@ -1,33 +1,181 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TruncateConfig } from '@ciderpress/config'
|
|
2
2
|
import { match, P } from 'massaman/match'
|
|
3
3
|
import type React from 'react'
|
|
4
4
|
|
|
5
5
|
import { useCiderpress } from '../../hooks/use-ciderpress'
|
|
6
|
-
import type { WorkspaceGroupData } from '../../hooks/use-ciderpress'
|
|
6
|
+
import type { WorkspaceCardData, WorkspaceGroupData } from '../../hooks/use-ciderpress'
|
|
7
|
+
import { renderRichText } from '../../lib/rich-text.tsx'
|
|
7
8
|
import { WorkspaceCard } from '../workspaces/card'
|
|
8
9
|
import { WorkspaceGrid } from '../workspaces/grid'
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* A single card compiled from `showcase.source` — the same shape as a
|
|
13
|
+
* workspace card, minus the grouping.
|
|
14
|
+
*/
|
|
15
|
+
export type ShowcaseCard = WorkspaceCardData
|
|
16
|
+
|
|
17
|
+
interface FrontmatterWorkspacesHeading {
|
|
18
|
+
readonly label?: string
|
|
19
|
+
readonly title?: string
|
|
20
|
+
readonly body?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface HomeWorkspacesProps {
|
|
24
|
+
readonly heading?: FrontmatterWorkspacesHeading
|
|
25
|
+
/**
|
|
26
|
+
* Cards compiled from an explicit `showcase.source` path list. When
|
|
27
|
+
* present they render as one ungrouped grid; otherwise the grouped
|
|
28
|
+
* workspace data from the theme config is used.
|
|
29
|
+
*/
|
|
30
|
+
readonly cards?: readonly ShowcaseCard[]
|
|
31
|
+
readonly columns?: 1 | 2 | 3 | 4
|
|
32
|
+
readonly truncate?: TruncateConfig
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const DEFAULT_COLUMNS: 1 | 2 | 3 | 4 = 2
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Showcase grid block. Renders either the cards compiled from an
|
|
39
|
+
* explicit `showcase.source` path list (one ungrouped grid) or the
|
|
40
|
+
* grouped workspace data serialized from the repo at build time. Grid
|
|
41
|
+
* layout — heading, columns, truncation — comes from props.
|
|
13
42
|
*
|
|
14
|
-
* @
|
|
43
|
+
* @param props - Optional heading, cards, column count, and truncation limits.
|
|
44
|
+
* @returns React element with the showcase grid, or null when empty.
|
|
15
45
|
*/
|
|
16
|
-
export function HomeWorkspaces(): React.ReactElement | null {
|
|
17
|
-
const { workspaces
|
|
18
|
-
const
|
|
46
|
+
export function HomeWorkspaces(props: HomeWorkspacesProps): React.ReactElement | null {
|
|
47
|
+
const { workspaces } = useCiderpress()
|
|
48
|
+
const { heading, cards, columns, truncate } = props
|
|
49
|
+
const resolvedColumns = match(columns)
|
|
50
|
+
.with(P.nonNullable, (c) => c)
|
|
51
|
+
.otherwise(() => DEFAULT_COLUMNS)
|
|
19
52
|
|
|
20
|
-
|
|
53
|
+
// A `cards` key means the block declared an explicit `showcase.source`
|
|
54
|
+
// — an empty array is "that source resolved to nothing", not "fall back
|
|
55
|
+
// to the workspace deck". Only an absent key takes the fallback.
|
|
56
|
+
const body = match(cards)
|
|
21
57
|
.with(
|
|
22
|
-
P.when((
|
|
23
|
-
(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
</div>
|
|
28
|
-
)
|
|
58
|
+
P.when((c): c is readonly ShowcaseCard[] => Array.isArray(c)),
|
|
59
|
+
(list) =>
|
|
60
|
+
match(list.length === 0)
|
|
61
|
+
.with(true, () => null)
|
|
62
|
+
.otherwise(() => [renderCardGrid(list, resolvedColumns, truncate)])
|
|
29
63
|
)
|
|
30
|
-
.otherwise(() =>
|
|
64
|
+
.otherwise(() =>
|
|
65
|
+
match(workspaces)
|
|
66
|
+
.with(
|
|
67
|
+
P.when((w): w is readonly WorkspaceGroupData[] => Array.isArray(w) && w.length > 0),
|
|
68
|
+
(groups) => groups.map((group, i) => renderGroup(group, resolvedColumns, truncate, i))
|
|
69
|
+
)
|
|
70
|
+
.otherwise(() => null)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return match(body)
|
|
74
|
+
.with(null, () => null)
|
|
75
|
+
.otherwise((content) => (
|
|
76
|
+
<div className="cp-workspace-section">
|
|
77
|
+
{/* The rule separates this band from the heading above it, so a
|
|
78
|
+
block with no heading of its own would render it as an orphan
|
|
79
|
+
line floating over the cards. */}
|
|
80
|
+
{match(renderHeading(heading))
|
|
81
|
+
.with(P.nullish, () => null)
|
|
82
|
+
.otherwise((head) => (
|
|
83
|
+
<>
|
|
84
|
+
<hr className="cp-divider" />
|
|
85
|
+
{head}
|
|
86
|
+
</>
|
|
87
|
+
))}
|
|
88
|
+
{content}
|
|
89
|
+
</div>
|
|
90
|
+
))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Render the block heading, or nothing when every field is absent.
|
|
95
|
+
*
|
|
96
|
+
* @private
|
|
97
|
+
* @param heading - Flat heading trio from the block
|
|
98
|
+
* @returns Heading element, or null
|
|
99
|
+
*/
|
|
100
|
+
function renderHeading(
|
|
101
|
+
heading: FrontmatterWorkspacesHeading | undefined
|
|
102
|
+
): React.ReactElement | null {
|
|
103
|
+
if (heading === undefined) {
|
|
104
|
+
return null
|
|
105
|
+
}
|
|
106
|
+
const { label, title, body } = heading
|
|
107
|
+
const hasContent = [label, title, body].some((value) => typeof value === 'string')
|
|
108
|
+
if (!hasContent) {
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
111
|
+
return (
|
|
112
|
+
<div className="cp-feature-section-head">
|
|
113
|
+
{match(label)
|
|
114
|
+
.with(P.string, (e) => (
|
|
115
|
+
<div className="cp-feature-section-head__eyebrow">{renderRichText(e)}</div>
|
|
116
|
+
))
|
|
117
|
+
.otherwise(() => null)}
|
|
118
|
+
{match(title)
|
|
119
|
+
.with(P.string, (t) => (
|
|
120
|
+
<h2 className="cp-feature-section-head__title">{renderRichText(t)}</h2>
|
|
121
|
+
))
|
|
122
|
+
.otherwise(() => null)}
|
|
123
|
+
{match(body)
|
|
124
|
+
.with(P.string, (s) => <p className="cp-feature-section-head__sub">{renderRichText(s)}</p>)
|
|
125
|
+
.otherwise(() => null)}
|
|
126
|
+
</div>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Render an ungrouped grid of cards compiled from `showcase.source`.
|
|
132
|
+
*
|
|
133
|
+
* @private
|
|
134
|
+
* @param cards - Resolved showcase cards
|
|
135
|
+
* @param columns - Column count for the grid
|
|
136
|
+
* @param truncate - Optional line-clamp limits for card text
|
|
137
|
+
* @returns Ungrouped workspace grid element
|
|
138
|
+
*/
|
|
139
|
+
function renderCardGrid(
|
|
140
|
+
cards: readonly ShowcaseCard[],
|
|
141
|
+
columns: 1 | 2 | 3 | 4,
|
|
142
|
+
truncate: TruncateConfig | undefined
|
|
143
|
+
): React.ReactElement {
|
|
144
|
+
return (
|
|
145
|
+
<WorkspaceGrid key="showcase-source" columns={columns}>
|
|
146
|
+
{cards.map((card, i) => renderCard(card, i, truncate))}
|
|
147
|
+
</WorkspaceGrid>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Render a single showcase / workspace card.
|
|
153
|
+
*
|
|
154
|
+
* @private
|
|
155
|
+
* @param card - Card data
|
|
156
|
+
* @param index - Array index for key generation
|
|
157
|
+
* @param truncate - Optional line-clamp limits for card text
|
|
158
|
+
* @returns Workspace card element
|
|
159
|
+
*/
|
|
160
|
+
function renderCard(
|
|
161
|
+
card: WorkspaceCardData,
|
|
162
|
+
index: number,
|
|
163
|
+
truncate: TruncateConfig | undefined
|
|
164
|
+
): React.ReactElement {
|
|
165
|
+
return (
|
|
166
|
+
<WorkspaceCard
|
|
167
|
+
key={`${card.title}-${index}`}
|
|
168
|
+
title={card.title}
|
|
169
|
+
href={card.href}
|
|
170
|
+
icon={card.icon}
|
|
171
|
+
scope={card.scope}
|
|
172
|
+
description={card.description}
|
|
173
|
+
tags={card.tags}
|
|
174
|
+
badge={card.badge}
|
|
175
|
+
titleLines={truncate && truncate.title}
|
|
176
|
+
descriptionLines={truncate && truncate.description}
|
|
177
|
+
/>
|
|
178
|
+
)
|
|
31
179
|
}
|
|
32
180
|
|
|
33
181
|
/**
|
|
@@ -35,37 +183,27 @@ export function HomeWorkspaces(): React.ReactElement | null {
|
|
|
35
183
|
*
|
|
36
184
|
* @private
|
|
37
185
|
* @param group - Workspace group data with heading, description, and cards
|
|
38
|
-
* @param
|
|
186
|
+
* @param columns - Column count for the grid
|
|
187
|
+
* @param truncate - Optional line-clamp limits for card text
|
|
188
|
+
* @param index - Position in the group list, to disambiguate the key
|
|
39
189
|
* @returns Workspace grid element
|
|
40
190
|
*/
|
|
41
191
|
function renderGroup(
|
|
42
192
|
group: WorkspaceGroupData,
|
|
43
|
-
|
|
193
|
+
columns: 1 | 2 | 3 | 4,
|
|
194
|
+
truncate: TruncateConfig | undefined,
|
|
195
|
+
index: number
|
|
44
196
|
): React.ReactElement {
|
|
45
|
-
const titleLines = gridConfig && gridConfig.truncate && gridConfig.truncate.title
|
|
46
|
-
const descLines = gridConfig && gridConfig.truncate && gridConfig.truncate.description
|
|
47
|
-
|
|
48
197
|
return (
|
|
198
|
+
// Heading alone is not unique — two configured workspace groups may
|
|
199
|
+
// share a title, which collided into one React key.
|
|
49
200
|
<WorkspaceGrid
|
|
50
|
-
key={group.heading}
|
|
201
|
+
key={`${group.heading}-${index}`}
|
|
51
202
|
heading={group.heading}
|
|
52
203
|
description={group.description}
|
|
53
|
-
columns={
|
|
204
|
+
columns={columns}
|
|
54
205
|
>
|
|
55
|
-
{group.cards.map((card, i) => (
|
|
56
|
-
<WorkspaceCard
|
|
57
|
-
key={`${card.title}-${i}`}
|
|
58
|
-
title={card.title}
|
|
59
|
-
href={card.href}
|
|
60
|
-
icon={card.icon}
|
|
61
|
-
scope={card.scope}
|
|
62
|
-
description={card.description}
|
|
63
|
-
tags={card.tags}
|
|
64
|
-
badge={card.badge}
|
|
65
|
-
titleLines={titleLines}
|
|
66
|
-
descriptionLines={descLines}
|
|
67
|
-
/>
|
|
68
|
-
))}
|
|
206
|
+
{group.cards.map((card, i) => renderCard(card, i, truncate))}
|
|
69
207
|
</WorkspaceGrid>
|
|
70
208
|
)
|
|
71
209
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { SiteConfig } from '@ciderpress/config'
|
|
2
1
|
import { match, P } from 'massaman/match'
|
|
3
2
|
import type React from 'react'
|
|
4
3
|
|
|
4
|
+
import type { CiderpressSiteBlock } from '../../hooks/use-ciderpress'
|
|
5
5
|
import { useCiderpress } from '../../hooks/use-ciderpress'
|
|
6
6
|
|
|
7
7
|
import './branch-tag.css'
|
|
@@ -12,7 +12,7 @@ declare const __CIDERPRESS_GIT_BRANCH__: string | undefined
|
|
|
12
12
|
/**
|
|
13
13
|
* Git branch tag — pill-shaped badge rendered via the `beforeNavMenu`
|
|
14
14
|
* layout slot. Hidden when on default branches (`main` or `master`) or
|
|
15
|
-
* when no `
|
|
15
|
+
* when no `editLink.repo` is configured. Uses the pixelarticons:git-branch
|
|
16
16
|
* icon.
|
|
17
17
|
*
|
|
18
18
|
* @returns React element, or `null` when on a default branch / unconfigured.
|
|
@@ -61,7 +61,7 @@ function resolveBranch(): string {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
* Build a GitHub `/tree/<branch>` URL from the configured `
|
|
64
|
+
* Build a GitHub `/tree/<branch>` URL from the configured `editLink.repo`.
|
|
65
65
|
*
|
|
66
66
|
* Accepts either a full URL (used as-is) or an `org/repo` slug
|
|
67
67
|
* (prefixed with `https://github.com/`). Returns `null` when `repo` is
|
|
@@ -83,14 +83,14 @@ function buildBranchHref(params: {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
|
-
* Pull the configured edit-repo slug off the site
|
|
87
|
-
* with explicit null checks rather than optional chaining.
|
|
86
|
+
* Pull the configured edit-repo slug off the serialised site block,
|
|
87
|
+
* expressed with explicit null checks rather than optional chaining.
|
|
88
88
|
*
|
|
89
89
|
* @private
|
|
90
|
-
* @param site - The
|
|
90
|
+
* @param site - The runtime site block (may be undefined).
|
|
91
91
|
* @returns The configured `edit.repo` value or `undefined`.
|
|
92
92
|
*/
|
|
93
|
-
function resolveRepo(site:
|
|
93
|
+
function resolveRepo(site: CiderpressSiteBlock | undefined): string | undefined {
|
|
94
94
|
if (site === undefined) {
|
|
95
95
|
return undefined
|
|
96
96
|
}
|
|
@@ -90,11 +90,21 @@
|
|
|
90
90
|
display: flex;
|
|
91
91
|
align-items: center;
|
|
92
92
|
gap: 6px;
|
|
93
|
-
|
|
93
|
+
/* Shrink-to-content (not grow) so page badges sit snug to the right of
|
|
94
|
+
the trail; long trails still truncate via `overflow: hidden`. */
|
|
95
|
+
flex: 0 1 auto;
|
|
94
96
|
min-width: 0;
|
|
95
97
|
overflow: hidden;
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
/* Page badges (ALPHA / WIP …) immediately right of the breadcrumb trail. */
|
|
101
|
+
.cp-docs-bar__badges {
|
|
102
|
+
display: inline-flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
gap: 4px;
|
|
105
|
+
flex: 0 0 auto;
|
|
106
|
+
}
|
|
107
|
+
|
|
98
108
|
.cp-docs-bar__crumb-wrap {
|
|
99
109
|
display: inline-flex;
|
|
100
110
|
align-items: center;
|
|
@@ -358,6 +368,8 @@ html[data-cp-outline-collapsed='true'] .cp-docs-bar__chev {
|
|
|
358
368
|
`styles/overrides/rspress.css`. ──────────────────────────────── */
|
|
359
369
|
.cp-docs-bar__llms {
|
|
360
370
|
flex-shrink: 0;
|
|
371
|
+
/* Crumbs no longer grow, so push the LLMs cluster to the far right. */
|
|
372
|
+
margin-left: auto;
|
|
361
373
|
}
|
|
362
374
|
|
|
363
375
|
.cp-docs-bar__llms .rp-llms-container {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { BadgeConfig } from '@ciderpress/config'
|
|
1
2
|
import { useFrontmatter, useLocation } from '@rspress/core/runtime'
|
|
2
3
|
import {
|
|
3
4
|
LlmsContainer,
|
|
@@ -13,8 +14,10 @@ import { match, P } from 'massaman/match'
|
|
|
13
14
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
14
15
|
import type React from 'react'
|
|
15
16
|
|
|
17
|
+
import { useCiderpress } from '../../hooks/use-ciderpress'
|
|
16
18
|
import { RouteLink } from '../../lib/route-link.tsx'
|
|
17
19
|
import { Icon } from '../shared/icon.tsx'
|
|
20
|
+
import { BadgeChips } from '../sidebar/sidebar-badge'
|
|
18
21
|
import { useCiderpressLayout } from './ciderpress-layout-context'
|
|
19
22
|
|
|
20
23
|
import './ciderpress-docs-bar.css'
|
|
@@ -117,6 +120,9 @@ export function CiderpressDocsBar(): React.ReactElement | null {
|
|
|
117
120
|
|
|
118
121
|
const pageTitle = readStringField(fmRecord.title)
|
|
119
122
|
|
|
123
|
+
const { pageBadges } = useCiderpress()
|
|
124
|
+
const badges = lookupPageBadges({ pageBadges, pathname })
|
|
125
|
+
|
|
120
126
|
const breadcrumbs = buildBreadcrumbs(pathname, pageTitle)
|
|
121
127
|
const outlineEnabled = match(fmRecord.outline)
|
|
122
128
|
.with(false, () => false)
|
|
@@ -174,12 +180,12 @@ export function CiderpressDocsBar(): React.ReactElement | null {
|
|
|
174
180
|
<span className="cp-docs-bar__sep">›</span>
|
|
175
181
|
{match(crumb.current)
|
|
176
182
|
.with(true, () => (
|
|
177
|
-
<span className="cp-docs-bar__crumb" aria-current="page">
|
|
183
|
+
<span className="cp-docs-bar__crumb" aria-current="page" title={crumb.label}>
|
|
178
184
|
{crumb.label}
|
|
179
185
|
</span>
|
|
180
186
|
))
|
|
181
187
|
.otherwise(() => (
|
|
182
|
-
<RouteLink href={crumb.href} className="cp-docs-bar__crumb">
|
|
188
|
+
<RouteLink href={crumb.href} className="cp-docs-bar__crumb" title={crumb.label}>
|
|
183
189
|
{crumb.label}
|
|
184
190
|
</RouteLink>
|
|
185
191
|
))}
|
|
@@ -187,6 +193,11 @@ export function CiderpressDocsBar(): React.ReactElement | null {
|
|
|
187
193
|
))}
|
|
188
194
|
</nav>
|
|
189
195
|
)}
|
|
196
|
+
{badges.length > 0 && (
|
|
197
|
+
<span className="cp-docs-bar__badges">
|
|
198
|
+
<BadgeChips badges={badges} />
|
|
199
|
+
</span>
|
|
200
|
+
)}
|
|
190
201
|
<div className="cp-docs-bar__llms">
|
|
191
202
|
<LlmsContainer>
|
|
192
203
|
<LlmsCopyButton />
|
|
@@ -254,6 +265,26 @@ interface Crumb {
|
|
|
254
265
|
readonly current: boolean
|
|
255
266
|
}
|
|
256
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Look up the current page's badges from the route→badges map, tolerating
|
|
270
|
+
* a trailing slash on the pathname (map keys are stored without one).
|
|
271
|
+
*
|
|
272
|
+
* @private
|
|
273
|
+
* @param params - The route→badges map (if present) and current pathname.
|
|
274
|
+
* @returns The page's badges, or an empty array when none apply.
|
|
275
|
+
*/
|
|
276
|
+
function lookupPageBadges(params: {
|
|
277
|
+
readonly pageBadges: Record<string, readonly BadgeConfig[]> | undefined
|
|
278
|
+
readonly pathname: string
|
|
279
|
+
}): readonly BadgeConfig[] {
|
|
280
|
+
const { pageBadges, pathname } = params
|
|
281
|
+
if (pageBadges === undefined) {
|
|
282
|
+
return []
|
|
283
|
+
}
|
|
284
|
+
const stripped = pathname.replace(/\/$/, '')
|
|
285
|
+
return pageBadges[pathname] ?? pageBadges[stripped] ?? []
|
|
286
|
+
}
|
|
287
|
+
|
|
257
288
|
/**
|
|
258
289
|
* Build a 1- or 2-level breadcrumb trail from the current path and the
|
|
259
290
|
* page's frontmatter title. The section label comes from titlecasing
|
|
@@ -2,19 +2,29 @@ import { clsx } from 'clsx'
|
|
|
2
2
|
import type React from 'react'
|
|
3
3
|
|
|
4
4
|
import { RouteLink } from '../../lib/route-link.tsx'
|
|
5
|
-
import { CiderpressLogo } from '../shared/ciderpress-logo'
|
|
6
5
|
import { CiderpressNavHamburger } from './ciderpress-nav-hamburger'
|
|
7
6
|
import { CiderpressNavMenu } from './ciderpress-nav-menu'
|
|
8
7
|
import type { CiderpressNavMenuItem } from './ciderpress-nav-menu'
|
|
9
8
|
import { CiderpressNavSearch } from './ciderpress-nav-search'
|
|
10
9
|
import { CiderpressNavSocialLinks } from './ciderpress-nav-social-links'
|
|
11
10
|
import type { CiderpressSocialLink } from './ciderpress-nav-social-links'
|
|
11
|
+
import { HeaderIcon } from './header-icon'
|
|
12
|
+
import { HeaderLogo } from './header-logo'
|
|
12
13
|
import { NavDivider } from './nav-divider'
|
|
13
14
|
import { TopbarCTA } from './topbar-cta'
|
|
14
15
|
import { VariantToggle } from './variant-toggle'
|
|
15
16
|
|
|
16
17
|
import './ciderpress-header.css'
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Build-time flag: `true` when the active theme declares more than one
|
|
21
|
+
* variant AND `data-cp-variants` won't collapse the `<VariantToggle />`
|
|
22
|
+
* to `display: none`. Drives whether the trailing-cluster divider is
|
|
23
|
+
* rendered — when the toggle is hidden and neither socials nor CTA
|
|
24
|
+
* follow, the divider has nothing to separate and is omitted.
|
|
25
|
+
*/
|
|
26
|
+
declare const __CIDERPRESS_HAS_VARIANT_TOGGLE__: boolean
|
|
27
|
+
|
|
18
28
|
/**
|
|
19
29
|
* Props for the ciderpress site header.
|
|
20
30
|
*/
|
|
@@ -57,6 +67,18 @@ export interface CiderpressHeaderProps {
|
|
|
57
67
|
* @returns Sticky header element
|
|
58
68
|
*/
|
|
59
69
|
export function CiderpressHeader(props: CiderpressHeaderProps): React.ReactElement {
|
|
70
|
+
// Dividers only render when they actually separate two visible
|
|
71
|
+
// clusters — orphan dividers (nothing on the right) are visually
|
|
72
|
+
// noisy. `<VariantToggle />` is hidden by CSS for single-variant
|
|
73
|
+
// themes; the build-time flag captures that decision so the React
|
|
74
|
+
// tree matches the painted output. The search/menu inner divider is
|
|
75
|
+
// unconditional — both clusters always render some chrome, and
|
|
76
|
+
// gating on `navItems.length` would flip after hydration (Rspress's
|
|
77
|
+
// nav data isn't populated at SSG) and produce a hydration warning.
|
|
78
|
+
const hasSocials = props.socialLinks.length > 0
|
|
79
|
+
const hasCta = props.topbarCta !== undefined
|
|
80
|
+
const hasTrailingCluster = __CIDERPRESS_HAS_VARIANT_TOGGLE__ || hasSocials || hasCta
|
|
81
|
+
|
|
60
82
|
// Landing / home pages live inside a constrained max-width container;
|
|
61
83
|
// doc pages stretch full-width (sidebar + article). The header mirrors
|
|
62
84
|
// the content shell so it never floats outside the page rhythm.
|
|
@@ -78,7 +100,8 @@ export function CiderpressHeader(props: CiderpressHeaderProps): React.ReactEleme
|
|
|
78
100
|
topbarCta={props.topbarCta}
|
|
79
101
|
/>
|
|
80
102
|
<RouteLink href="/" className="cp-header-logo" aria-label="Home">
|
|
81
|
-
<
|
|
103
|
+
<HeaderIcon />
|
|
104
|
+
<HeaderLogo />
|
|
82
105
|
</RouteLink>
|
|
83
106
|
|
|
84
107
|
{/* Right cluster wrapper. Grows from a zero basis so its width
|
|
@@ -96,7 +119,7 @@ export function CiderpressHeader(props: CiderpressHeaderProps): React.ReactEleme
|
|
|
96
119
|
<CiderpressNavMenu items={props.navItems} />
|
|
97
120
|
</div>
|
|
98
121
|
|
|
99
|
-
<NavDivider />
|
|
122
|
+
{hasTrailingCluster && <NavDivider />}
|
|
100
123
|
<VariantToggle />
|
|
101
124
|
<CiderpressNavSocialLinks links={props.socialLinks} />
|
|
102
125
|
{props.topbarCta !== undefined && (
|
|
@@ -141,6 +141,56 @@
|
|
|
141
141
|
text-decoration: none;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
.cp-nav-drawer__group {
|
|
145
|
+
display: flex;
|
|
146
|
+
flex-direction: column;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.cp-nav-drawer__group-toggle {
|
|
150
|
+
display: flex;
|
|
151
|
+
align-items: center;
|
|
152
|
+
justify-content: space-between;
|
|
153
|
+
width: 100%;
|
|
154
|
+
padding: 12px 16px;
|
|
155
|
+
background: transparent;
|
|
156
|
+
border: none;
|
|
157
|
+
border-radius: 8px;
|
|
158
|
+
color: var(--cp-c-text-1);
|
|
159
|
+
font-family: inherit;
|
|
160
|
+
font-size: 15px;
|
|
161
|
+
font-weight: 500;
|
|
162
|
+
text-align: left;
|
|
163
|
+
cursor: pointer;
|
|
164
|
+
transition:
|
|
165
|
+
background 0.12s ease,
|
|
166
|
+
color 0.12s ease;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.cp-nav-drawer__group-toggle:hover {
|
|
170
|
+
background: var(--cp-c-bg-soft);
|
|
171
|
+
color: var(--cp-c-brand-1);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.cp-nav-drawer__group-chevron {
|
|
175
|
+
flex-shrink: 0;
|
|
176
|
+
color: var(--cp-c-text-2);
|
|
177
|
+
transition: transform 0.18s ease;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.cp-nav-drawer__group-toggle[aria-expanded='true'] .cp-nav-drawer__group-chevron {
|
|
181
|
+
transform: rotate(180deg);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.cp-nav-drawer__group-items {
|
|
185
|
+
display: flex;
|
|
186
|
+
flex-direction: column;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.cp-nav-drawer__link--nested {
|
|
190
|
+
padding-left: 28px;
|
|
191
|
+
font-size: 14px;
|
|
192
|
+
}
|
|
193
|
+
|
|
144
194
|
.cp-nav-drawer__divider {
|
|
145
195
|
height: 1px;
|
|
146
196
|
margin: 4px 20px;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { match } from 'massaman/match'
|
|
1
2
|
import { useState } from 'react'
|
|
2
3
|
import type React from 'react'
|
|
3
4
|
import { Dialog, Modal, ModalOverlay } from 'react-aria-components'
|
|
@@ -71,15 +72,12 @@ export function CiderpressNavHamburger(props: CiderpressNavHamburgerProps): Reac
|
|
|
71
72
|
</header>
|
|
72
73
|
|
|
73
74
|
<nav className="cp-nav-drawer__nav" aria-label="Primary">
|
|
74
|
-
{props.navItems.map((item) => (
|
|
75
|
-
<
|
|
76
|
-
key={item
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
>
|
|
81
|
-
{item.text}
|
|
82
|
-
</RouteLink>
|
|
75
|
+
{props.navItems.map((item, index) => (
|
|
76
|
+
<DrawerNavEntry
|
|
77
|
+
key={`${drawerItemKey(item)}::${index}`}
|
|
78
|
+
item={item}
|
|
79
|
+
onNavigate={() => setOpen(false)}
|
|
80
|
+
/>
|
|
83
81
|
))}
|
|
84
82
|
</nav>
|
|
85
83
|
|
|
@@ -110,3 +108,91 @@ export function CiderpressNavHamburger(props: CiderpressNavHamburgerProps): Reac
|
|
|
110
108
|
}
|
|
111
109
|
|
|
112
110
|
export { CiderpressNavHamburger as default }
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Render one entry in the mobile drawer nav. Leaf items are a single
|
|
114
|
+
* link; dropdown parents become a collapsible {@link DrawerNavGroup}.
|
|
115
|
+
*
|
|
116
|
+
* @private
|
|
117
|
+
* @param props - The nav item and a callback to close the drawer.
|
|
118
|
+
* @returns The drawer entry element.
|
|
119
|
+
*/
|
|
120
|
+
function DrawerNavEntry(props: {
|
|
121
|
+
readonly item: CiderpressNavMenuItem
|
|
122
|
+
readonly onNavigate: () => void
|
|
123
|
+
}): React.ReactElement {
|
|
124
|
+
const { item, onNavigate } = props
|
|
125
|
+
const children = item.items ?? []
|
|
126
|
+
return match(children.length > 0)
|
|
127
|
+
.with(true, () => <DrawerNavGroup item={item} onNavigate={onNavigate} />)
|
|
128
|
+
.otherwise(() => (
|
|
129
|
+
<RouteLink href={item.link ?? '#'} className="cp-nav-drawer__link" onClick={onNavigate}>
|
|
130
|
+
{item.text}
|
|
131
|
+
</RouteLink>
|
|
132
|
+
))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* A collapsible dropdown section in the mobile drawer: a tappable header
|
|
137
|
+
* that toggles its child links open/closed. Collapsed by default so a
|
|
138
|
+
* deep nav doesn't fill the drawer on open.
|
|
139
|
+
*
|
|
140
|
+
* @private
|
|
141
|
+
* @param props - The dropdown item and a callback to close the drawer.
|
|
142
|
+
* @returns The collapsible group element.
|
|
143
|
+
*/
|
|
144
|
+
function DrawerNavGroup(props: {
|
|
145
|
+
readonly item: CiderpressNavMenuItem
|
|
146
|
+
readonly onNavigate: () => void
|
|
147
|
+
}): React.ReactElement {
|
|
148
|
+
const { item, onNavigate } = props
|
|
149
|
+
const [expanded, setExpanded] = useState(false)
|
|
150
|
+
const children = item.items ?? []
|
|
151
|
+
return (
|
|
152
|
+
<div className="cp-nav-drawer__group" role="group" aria-label={item.text}>
|
|
153
|
+
<button
|
|
154
|
+
type="button"
|
|
155
|
+
className="cp-nav-drawer__group-toggle"
|
|
156
|
+
onClick={() => setExpanded((current) => !current)}
|
|
157
|
+
aria-expanded={expanded}
|
|
158
|
+
>
|
|
159
|
+
<span>{item.text}</span>
|
|
160
|
+
<Icon
|
|
161
|
+
className="cp-nav-drawer__group-chevron"
|
|
162
|
+
icon="pixelarticons:chevron-down"
|
|
163
|
+
width={16}
|
|
164
|
+
height={16}
|
|
165
|
+
/>
|
|
166
|
+
</button>
|
|
167
|
+
{expanded && (
|
|
168
|
+
<div className="cp-nav-drawer__group-items">
|
|
169
|
+
{children.map((child, index) => (
|
|
170
|
+
<RouteLink
|
|
171
|
+
key={`${drawerItemKey(child)}::${index}`}
|
|
172
|
+
href={child.link ?? '#'}
|
|
173
|
+
className="cp-nav-drawer__link cp-nav-drawer__link--nested"
|
|
174
|
+
onClick={onNavigate}
|
|
175
|
+
>
|
|
176
|
+
{child.text}
|
|
177
|
+
</RouteLink>
|
|
178
|
+
))}
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Stable React key for a drawer nav item — its link when present,
|
|
187
|
+
* otherwise its label (dropdown parents may have no link of their own).
|
|
188
|
+
*
|
|
189
|
+
* @private
|
|
190
|
+
* @param item - Nav item to key.
|
|
191
|
+
* @returns Key string.
|
|
192
|
+
*/
|
|
193
|
+
function drawerItemKey(item: CiderpressNavMenuItem): string {
|
|
194
|
+
if (item.link !== undefined && item.link !== '') {
|
|
195
|
+
return item.link
|
|
196
|
+
}
|
|
197
|
+
return item.text
|
|
198
|
+
}
|