@ciderpress/ui 1.0.0-rc.6 → 1.0.0-rc.7
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/node.mjs +20 -2
- package/dist/plugins/mermaid/MermaidRenderer.tsx +24 -6
- 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-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 +18 -15
- package/dist/theme/components/nav/layout.tsx +72 -9
- package/dist/theme/components/shared/icon.tsx +2 -1
- package/dist/theme/components/shared/section-card.tsx +28 -0
- 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-scope.tsx +24 -1
- package/dist/theme/hooks/use-ciderpress.ts +6 -1
- package/dist/theme/hooks/use-nav-items.ts +82 -11
- package/dist/theme/index.tsx +3 -0
- package/dist/theme/styles/overrides/rail.css +27 -10
- package/dist/theme/styles/overrides/section-card.css +9 -0
- package/dist/theme/styles/overrides/sidebar.css +22 -0
- package/package.json +13 -12
- 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-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 +18 -15
- package/src/theme/components/nav/layout.tsx +72 -9
- package/src/theme/components/shared/icon.tsx +2 -1
- package/src/theme/components/shared/section-card.tsx +28 -0
- 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-scope.tsx +24 -1
- package/src/theme/hooks/use-ciderpress.ts +6 -1
- package/src/theme/hooks/use-nav-items.ts +82 -11
- package/src/theme/index.tsx +3 -0
- package/src/theme/styles/overrides/rail.css +27 -10
- package/src/theme/styles/overrides/section-card.css +9 -0
- package/src/theme/styles/overrides/sidebar.css +22 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import type { BadgeConfig } from '@ciderpress/config'
|
|
1
2
|
import { match, P } from 'massaman/match'
|
|
2
3
|
import type React from 'react'
|
|
3
4
|
|
|
5
|
+
import { useCiderpress } from '../../hooks/use-ciderpress'
|
|
6
|
+
import { BadgeChips } from '../sidebar/sidebar-badge'
|
|
4
7
|
import { Card } from './card'
|
|
5
8
|
import { CardIcon } from './card-icon'
|
|
6
9
|
import type { CardIconInput } from './resolve-card-icon'
|
|
@@ -26,6 +29,8 @@ export function SectionCard({
|
|
|
26
29
|
description,
|
|
27
30
|
icon = 'pixelarticons:file',
|
|
28
31
|
}: SectionCardProps): React.ReactElement {
|
|
32
|
+
const { pageBadges } = useCiderpress()
|
|
33
|
+
const badges = lookupBadges({ pageBadges, href })
|
|
29
34
|
const resolved = resolveCardIcon(icon) ?? {
|
|
30
35
|
kind: 'iconify' as const,
|
|
31
36
|
id: 'pixelarticons:file',
|
|
@@ -40,8 +45,31 @@ export function SectionCard({
|
|
|
40
45
|
<div className="cp-section-card__header">
|
|
41
46
|
<CardIcon resolved={resolved} className="cp-section-card__icon" />
|
|
42
47
|
<span className="cp-section-card__title">{title}</span>
|
|
48
|
+
{badges.length > 0 && (
|
|
49
|
+
<span className="cp-section-card__badges">
|
|
50
|
+
<BadgeChips badges={badges} />
|
|
51
|
+
</span>
|
|
52
|
+
)}
|
|
43
53
|
</div>
|
|
44
54
|
{descEl}
|
|
45
55
|
</Card>
|
|
46
56
|
)
|
|
47
57
|
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Look up a page's badges from the route→badges map by its href.
|
|
61
|
+
*
|
|
62
|
+
* @private
|
|
63
|
+
* @param params - The route→badges map (if present) and card destination.
|
|
64
|
+
* @returns The page's badges, or an empty array when none apply
|
|
65
|
+
*/
|
|
66
|
+
function lookupBadges(params: {
|
|
67
|
+
readonly pageBadges: Record<string, readonly BadgeConfig[]> | undefined
|
|
68
|
+
readonly href: string
|
|
69
|
+
}): readonly BadgeConfig[] {
|
|
70
|
+
const { pageBadges, href } = params
|
|
71
|
+
if (pageBadges === undefined) {
|
|
72
|
+
return []
|
|
73
|
+
}
|
|
74
|
+
return pageBadges[href] ?? []
|
|
75
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar badge chips — small uppercase labels (ALPHA, WIP, …) rendered
|
|
3
|
+
* in a sidebar item's right slot via the `Tag` override.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
@layer ciderpress.overrides {
|
|
7
|
+
.cp-sbadge {
|
|
8
|
+
display: inline-flex;
|
|
9
|
+
align-items: center;
|
|
10
|
+
padding: 1px 5px;
|
|
11
|
+
border-radius: var(--cp-radius-pill, 9999px);
|
|
12
|
+
font-family: var(--cp-font-family-mono, inherit);
|
|
13
|
+
font-size: 9px;
|
|
14
|
+
font-weight: 600;
|
|
15
|
+
line-height: 1.4;
|
|
16
|
+
letter-spacing: 0.04em;
|
|
17
|
+
text-transform: uppercase;
|
|
18
|
+
white-space: nowrap;
|
|
19
|
+
vertical-align: middle;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.cp-sbadge + .cp-sbadge {
|
|
23
|
+
margin-left: 4px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.cp-sbadge--info {
|
|
27
|
+
background: var(--cp-c-badge-info-bg);
|
|
28
|
+
color: var(--cp-c-badge-info-fg);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.cp-sbadge--success {
|
|
32
|
+
background: var(--cp-c-badge-success-bg);
|
|
33
|
+
color: var(--cp-c-badge-success-fg);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.cp-sbadge--warning {
|
|
37
|
+
background: var(--cp-c-badge-warning-bg);
|
|
38
|
+
color: var(--cp-c-badge-warning-fg);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.cp-sbadge--danger {
|
|
42
|
+
background: var(--cp-c-badge-error-bg);
|
|
43
|
+
color: var(--cp-c-badge-error-fg);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.cp-sbadge--neutral {
|
|
47
|
+
background: var(--rp-c-bg-mute);
|
|
48
|
+
color: var(--rp-c-text-2);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar badge rendering — overrides Rspress's `Tag` component.
|
|
3
|
+
*
|
|
4
|
+
* Rspress renders `<Tag tag={...} />` inside every sidebar item. Ciderpress
|
|
5
|
+
* encodes page badges (label + variant + tooltip) into that `tag` string via
|
|
6
|
+
* `@ciderpress/config`'s `encodeBadges`. This override decodes ciderpress
|
|
7
|
+
* badges and renders styled chips; any other tag falls through to Rspress's
|
|
8
|
+
* native `Tag` so existing tag behavior (images, keyword badges) is preserved.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { decodeBadges } from '@ciderpress/config'
|
|
12
|
+
import type { BadgeConfig, BadgeVariant } from '@ciderpress/config'
|
|
13
|
+
import { Tag as RspressTag } from '@rspress/core/theme-original'
|
|
14
|
+
import { match, P } from 'massaman/match'
|
|
15
|
+
import type React from 'react'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Props mirror Rspress's `Tag` — a single (optional) tag string.
|
|
19
|
+
*
|
|
20
|
+
* @private
|
|
21
|
+
*/
|
|
22
|
+
interface TagProps {
|
|
23
|
+
readonly tag?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Sidebar tag renderer. Renders ciderpress badge chips for encoded tags,
|
|
28
|
+
* delegating everything else to Rspress's native `Tag`.
|
|
29
|
+
*
|
|
30
|
+
* @param props - The sidebar item's tag string
|
|
31
|
+
* @returns Badge chips, a native tag, or `null` when there is no tag
|
|
32
|
+
*/
|
|
33
|
+
export function Tag({ tag }: TagProps): React.ReactElement | null {
|
|
34
|
+
const badges = decodeTag(tag)
|
|
35
|
+
if (badges === null) {
|
|
36
|
+
return <RspressTag tag={tag} />
|
|
37
|
+
}
|
|
38
|
+
return <BadgeChips badges={badges} />
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Props for {@link BadgeChips}.
|
|
43
|
+
*/
|
|
44
|
+
export interface BadgeChipsProps {
|
|
45
|
+
readonly badges: readonly BadgeConfig[]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Render a list of badge chips (variant color, custom-color tint, hover
|
|
50
|
+
* tooltip). Used by the sidebar `Tag` override and the breadcrumb bar.
|
|
51
|
+
*
|
|
52
|
+
* @param props - The badges to render
|
|
53
|
+
* @returns Chip elements, or `null` when there are no badges
|
|
54
|
+
*/
|
|
55
|
+
export function BadgeChips({ badges }: BadgeChipsProps): React.ReactElement | null {
|
|
56
|
+
if (badges.length === 0) {
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
{badges.map((badge, index) => (
|
|
62
|
+
<SidebarBadge key={`${badge.text}-${String(index)}`} badge={badge} />
|
|
63
|
+
))}
|
|
64
|
+
</>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Decode a tag string into ciderpress badges, or `null` when the tag is
|
|
70
|
+
* absent or not ciderpress-encoded.
|
|
71
|
+
*
|
|
72
|
+
* @private
|
|
73
|
+
* @param tag - Raw tag string from the sidebar item
|
|
74
|
+
* @returns Decoded badges, or `null` to signal native fallback
|
|
75
|
+
*/
|
|
76
|
+
function decodeTag(tag: string | undefined): readonly BadgeConfig[] | null {
|
|
77
|
+
if (tag === undefined) {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
return decodeBadges(tag)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A single sidebar badge chip.
|
|
85
|
+
*
|
|
86
|
+
* @private
|
|
87
|
+
*/
|
|
88
|
+
interface SidebarBadgeProps {
|
|
89
|
+
readonly badge: BadgeConfig
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Render one badge chip with variant color (or a custom color tint) and a
|
|
94
|
+
* hover tooltip.
|
|
95
|
+
*
|
|
96
|
+
* @private
|
|
97
|
+
* @param props - The badge to render
|
|
98
|
+
* @returns A styled badge chip element
|
|
99
|
+
*/
|
|
100
|
+
function SidebarBadge({ badge }: SidebarBadgeProps): React.ReactElement {
|
|
101
|
+
const tooltip = resolveTooltip(badge)
|
|
102
|
+
return match(badge.color)
|
|
103
|
+
.with(P.string, (color) => (
|
|
104
|
+
<span className="cp-sbadge" style={{ color, backgroundColor: tint(color) }} title={tooltip}>
|
|
105
|
+
{badge.text}
|
|
106
|
+
</span>
|
|
107
|
+
))
|
|
108
|
+
.otherwise(() => (
|
|
109
|
+
<span className={variantClass(badge.variant)} title={tooltip}>
|
|
110
|
+
{badge.text}
|
|
111
|
+
</span>
|
|
112
|
+
))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Resolve the hover tooltip for a badge, defaulting to its label text.
|
|
117
|
+
*
|
|
118
|
+
* @private
|
|
119
|
+
* @param badge - The badge to resolve a tooltip for
|
|
120
|
+
* @returns The tooltip string
|
|
121
|
+
*/
|
|
122
|
+
function resolveTooltip(badge: BadgeConfig): string {
|
|
123
|
+
return badge.tooltip ?? badge.text
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Map a badge variant to its chip class, defaulting to `neutral`.
|
|
128
|
+
*
|
|
129
|
+
* @private
|
|
130
|
+
* @param variant - The badge variant, if any
|
|
131
|
+
* @returns The full class string for the chip
|
|
132
|
+
*/
|
|
133
|
+
function variantClass(variant: BadgeVariant | undefined): string {
|
|
134
|
+
return match(variant)
|
|
135
|
+
.with('info', () => 'cp-sbadge cp-sbadge--info')
|
|
136
|
+
.with('success', () => 'cp-sbadge cp-sbadge--success')
|
|
137
|
+
.with('warning', () => 'cp-sbadge cp-sbadge--warning')
|
|
138
|
+
.with('danger', () => 'cp-sbadge cp-sbadge--danger')
|
|
139
|
+
.with('neutral', () => 'cp-sbadge cp-sbadge--neutral')
|
|
140
|
+
.with(undefined, () => 'cp-sbadge cp-sbadge--neutral')
|
|
141
|
+
.exhaustive()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Build a subtle background tint from a custom color.
|
|
146
|
+
*
|
|
147
|
+
* @private
|
|
148
|
+
* @param color - CSS color value
|
|
149
|
+
* @returns A `color-mix` background at low opacity
|
|
150
|
+
*/
|
|
151
|
+
function tint(color: string): string {
|
|
152
|
+
return `color-mix(in srgb, ${color} 14%, transparent)`
|
|
153
|
+
}
|
|
@@ -14,7 +14,7 @@ import type { SidebarData } from '@rspress/core'
|
|
|
14
14
|
import { useActiveMatcher, useLocation, useSidebar } from '@rspress/core/runtime'
|
|
15
15
|
import { SidebarList } from '@rspress/core/theme-original'
|
|
16
16
|
import type React from 'react'
|
|
17
|
-
import { useLayoutEffect, useMemo, useState } from 'react'
|
|
17
|
+
import { useEffect, useLayoutEffect, useMemo, useState } from 'react'
|
|
18
18
|
|
|
19
19
|
import { useCiderpress } from '../../hooks/use-ciderpress'
|
|
20
20
|
import { resolveScopedSidebar } from './sidebar-filter'
|
|
@@ -59,9 +59,32 @@ export function Sidebar(): React.ReactElement {
|
|
|
59
59
|
setSidebarData(initializeCollapsed(filteredData, activeMatcher))
|
|
60
60
|
}, [activeMatcher, filteredData])
|
|
61
61
|
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
decorateOverflowTitles()
|
|
64
|
+
}, [sidebarData])
|
|
65
|
+
|
|
62
66
|
return <SidebarList sidebarData={sidebarData} setSidebarData={setSidebarData} />
|
|
63
67
|
}
|
|
64
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Add a `title` attribute to sidebar labels that overflow their column so
|
|
71
|
+
* the full text shows on hover; clear it when the label fits. Runs after
|
|
72
|
+
* render because overflow is a measured, layout-dependent property.
|
|
73
|
+
*
|
|
74
|
+
* @private
|
|
75
|
+
*/
|
|
76
|
+
function decorateOverflowTitles(): void {
|
|
77
|
+
const labels = [...document.querySelectorAll<HTMLElement>('.rp-sidebar-item__left > .rp-doc')]
|
|
78
|
+
labels.reduce<null>((_, label) => {
|
|
79
|
+
if (label.scrollWidth > label.clientWidth) {
|
|
80
|
+
label.setAttribute('title', label.textContent ?? '')
|
|
81
|
+
} else {
|
|
82
|
+
label.removeAttribute('title')
|
|
83
|
+
}
|
|
84
|
+
return null
|
|
85
|
+
}, null)
|
|
86
|
+
}
|
|
87
|
+
|
|
65
88
|
/**
|
|
66
89
|
* Walk the sidebar tree and uncollapse groups that contain the active path.
|
|
67
90
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FooterConfig, HomeConfig, SerializedIcon } 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 {
|
|
@@ -86,6 +86,10 @@ export interface CiderpressSiteBlock {
|
|
|
86
86
|
readonly brandMark?: string
|
|
87
87
|
}
|
|
88
88
|
| undefined
|
|
89
|
+
readonly feedback: {
|
|
90
|
+
readonly enabled: boolean
|
|
91
|
+
readonly question: string | undefined
|
|
92
|
+
}
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
interface CiderpressThemeConfig {
|
|
@@ -97,6 +101,7 @@ interface CiderpressThemeConfig {
|
|
|
97
101
|
readonly home: HomeConfig | undefined
|
|
98
102
|
readonly ciderpressFooter: FooterConfig | undefined
|
|
99
103
|
readonly site: CiderpressSiteBlock | undefined
|
|
104
|
+
readonly pageBadges: Record<string, readonly BadgeConfig[]> | undefined
|
|
100
105
|
}
|
|
101
106
|
|
|
102
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
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
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
|
-
|
|
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:
|
|
56
|
-
|
|
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
|
|
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
|
|
135
|
+
* this element".
|
|
65
136
|
*
|
|
66
137
|
* @private
|
|
67
|
-
* @param
|
|
138
|
+
* @param element - Element to read.
|
|
68
139
|
* @returns Trimmed inner text, or empty string when absent.
|
|
69
140
|
*/
|
|
70
|
-
function
|
|
71
|
-
const text =
|
|
141
|
+
function readElementText(element: Element): string {
|
|
142
|
+
const text = element.textContent
|
|
72
143
|
if (text === null) {
|
|
73
144
|
return ''
|
|
74
145
|
}
|
package/dist/theme/index.tsx
CHANGED
|
@@ -53,6 +53,7 @@ import './components/content-footer/feedback.css'
|
|
|
53
53
|
import './components/content-footer/meta-actions.css'
|
|
54
54
|
import './components/content-footer/page-pager.css'
|
|
55
55
|
import './components/sidebar/framework-picker.css'
|
|
56
|
+
import './components/sidebar/sidebar-badge.css'
|
|
56
57
|
import './components/sidebar/sidebar-promo.css'
|
|
57
58
|
import './components/sidebar/sidebar-toggle.css'
|
|
58
59
|
import './components/home/page-rail.css'
|
|
@@ -89,6 +90,8 @@ export { HomeFeature } from './components/home/feature'
|
|
|
89
90
|
export { HomeLayout } from './components/home/layout'
|
|
90
91
|
/** @internal Sidebar override — multi-scope filtering for standalone sections */
|
|
91
92
|
export { Sidebar } from './components/sidebar/sidebar-scope'
|
|
93
|
+
/** @internal Tag override — renders ciderpress sidebar badges, delegates other tags to Rspress */
|
|
94
|
+
export { Tag } from './components/sidebar/sidebar-badge'
|
|
92
95
|
|
|
93
96
|
export { FeatureCard, FeatureGrid } from './components/home/feature-card'
|
|
94
97
|
export type { FeatureCardProps, FeatureItem } from './components/home/feature-card'
|
|
@@ -145,18 +145,22 @@ html .rp-doc-layout__sidebar > :first-child:not(.cp-sidebar-top) {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
html .cp-sidebar-bottom {
|
|
148
|
-
/*
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
/* Size to content and sit directly under the nav tree. `flex: 0 0 auto`
|
|
149
|
+
(no grow) is deliberate: growing to fill (`1 0 auto`) stretched the
|
|
150
|
+
band across all leftover sidebar height, and a short nav tree left a
|
|
151
|
+
tall empty band with its content (promo + below-links) stranded at the
|
|
152
|
+
bottom. With no grow the band is exactly as tall as its content and
|
|
153
|
+
the promo/links hug the last nav item. */
|
|
154
|
+
flex: 0 0 auto;
|
|
153
155
|
display: flex;
|
|
154
156
|
flex-direction: column;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
/* When the nav tree overflows the viewport the sidebar scrolls; this
|
|
158
|
+
band stays glued to the viewport bottom (`sticky bottom: 0`) so the
|
|
159
|
+
promo/links remain visible while the tree scrolls behind them. With a
|
|
160
|
+
short (non-scrolling) nav tree the sticky offset is never engaged, so
|
|
161
|
+
the band simply flows under the last nav item. `z-index: 3` paints it
|
|
162
|
+
above the thin overlay scrollbar thumb so it doesn't appear to "go
|
|
163
|
+
through" the band region. */
|
|
160
164
|
position: sticky;
|
|
161
165
|
bottom: 0;
|
|
162
166
|
z-index: 3;
|
|
@@ -189,6 +193,19 @@ html .rp-outline__title {
|
|
|
189
193
|
display: none;
|
|
190
194
|
}
|
|
191
195
|
|
|
196
|
+
/* "On this page" outline — truncate long headings to a single line with
|
|
197
|
+
an ellipsis instead of wrapping. Rspress already sets a native `title`
|
|
198
|
+
on each `.rp-toc-item` anchor, so hovering a truncated heading reveals
|
|
199
|
+
the full text. Targets `.rp-toc-item__text`, shared by the desktop
|
|
200
|
+
rail and the mobile outline dropdown. */
|
|
201
|
+
html .rp-toc-item__text {
|
|
202
|
+
display: block;
|
|
203
|
+
min-width: 0;
|
|
204
|
+
overflow: hidden;
|
|
205
|
+
text-overflow: ellipsis;
|
|
206
|
+
white-space: nowrap;
|
|
207
|
+
}
|
|
208
|
+
|
|
192
209
|
html .rp-doc-layout__doc {
|
|
193
210
|
background: var(--cp-c-bg);
|
|
194
211
|
}
|
|
@@ -31,6 +31,15 @@
|
|
|
31
31
|
gap: 10px;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/* Page badges pinned to the right of the card title row. */
|
|
35
|
+
.cp-section-card__badges {
|
|
36
|
+
margin-left: auto;
|
|
37
|
+
display: inline-flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
gap: 4px;
|
|
40
|
+
flex: 0 0 auto;
|
|
41
|
+
}
|
|
42
|
+
|
|
34
43
|
.cp-section-card:hover {
|
|
35
44
|
border-color: var(--cp-c-brand-1);
|
|
36
45
|
background: var(--cp-c-bg-elv);
|
|
@@ -24,6 +24,28 @@
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/* ── Long titles — single-line ellipsis, badge stays pinned ──── */
|
|
28
|
+
/* The item's left slot holds the label; constrain it so the label
|
|
29
|
+
truncates instead of pushing the badge (right slot) out of view.
|
|
30
|
+
A `title` attribute is added by the Sidebar effect on overflow. */
|
|
31
|
+
html .rp-sidebar-item__left {
|
|
32
|
+
min-width: 0;
|
|
33
|
+
flex: 1 1 auto;
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
html .rp-sidebar-item__left > .rp-doc {
|
|
38
|
+
min-width: 0;
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
text-overflow: ellipsis;
|
|
41
|
+
white-space: nowrap;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
html .rp-sidebar-item__right {
|
|
45
|
+
flex: 0 0 auto;
|
|
46
|
+
margin-left: 6px;
|
|
47
|
+
}
|
|
48
|
+
|
|
27
49
|
/* ── Group section headers — mono ALL CAPS (mockup pattern) ──── */
|
|
28
50
|
html .rp-sidebar-section-header {
|
|
29
51
|
font-family: var(--cp-font-family-mono);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ciderpress/ui",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.7",
|
|
4
4
|
"description": "Rspress plugin, theme components, and styles for ciderpress",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ciderpress",
|
|
@@ -48,28 +48,29 @@
|
|
|
48
48
|
"@iconify-json/catppuccin": "^1.2.17",
|
|
49
49
|
"@iconify-json/devicon": "^1.2.62",
|
|
50
50
|
"@iconify-json/logos": "^1.2.11",
|
|
51
|
-
"@iconify-json/material-icon-theme": "^1.2.
|
|
51
|
+
"@iconify-json/material-icon-theme": "^1.2.69",
|
|
52
52
|
"@iconify-json/mdi": "^1.2.3",
|
|
53
|
-
"@iconify-json/
|
|
54
|
-
"@iconify-json/
|
|
53
|
+
"@iconify-json/pixel": "^1.2.2",
|
|
54
|
+
"@iconify-json/pixelarticons": "^1.2.7",
|
|
55
|
+
"@iconify-json/simple-icons": "^1.2.89",
|
|
55
56
|
"@iconify-json/skill-icons": "^1.2.4",
|
|
56
|
-
"@iconify-json/vscode-icons": "^1.2.
|
|
57
|
+
"@iconify-json/vscode-icons": "^1.2.65",
|
|
57
58
|
"@iconify/react": "^6.0.2",
|
|
58
59
|
"clsx": "^2.1.1",
|
|
59
60
|
"katex": "^0.17.0",
|
|
60
61
|
"massaman": "^0.3.0",
|
|
61
|
-
"mermaid": "^
|
|
62
|
+
"mermaid": "^11.16.0",
|
|
62
63
|
"openapi-sampler": "^1.7.4",
|
|
63
64
|
"react-aria-components": "^1.19.0",
|
|
64
65
|
"ts-morph": "^28.0.0",
|
|
65
66
|
"unist-util-visit": "^5.1.0",
|
|
66
|
-
"@ciderpress/config": "1.0.0-rc.
|
|
67
|
-
"@ciderpress/theme": "1.0.0-rc.
|
|
67
|
+
"@ciderpress/config": "1.0.0-rc.6",
|
|
68
|
+
"@ciderpress/theme": "1.0.0-rc.4"
|
|
68
69
|
},
|
|
69
70
|
"devDependencies": {
|
|
70
71
|
"@ladle/react": "^5.1.1",
|
|
71
|
-
"@rslib/core": "
|
|
72
|
-
"@rspress/core": "^2.0.
|
|
72
|
+
"@rslib/core": "0.23.1",
|
|
73
|
+
"@rspress/core": "^2.0.16",
|
|
73
74
|
"@types/react": "^19.2.17",
|
|
74
75
|
"@types/react-dom": "^19.2.3",
|
|
75
76
|
"esbuild": "^0.28.1",
|
|
@@ -81,10 +82,10 @@
|
|
|
81
82
|
"rspress-plugin-katex": "^1.0.1",
|
|
82
83
|
"rspress-plugin-supersub": "^1.0.0",
|
|
83
84
|
"typescript": "^6.0.3",
|
|
84
|
-
"vitest": "^4.1.
|
|
85
|
+
"vitest": "^4.1.10"
|
|
85
86
|
},
|
|
86
87
|
"peerDependencies": {
|
|
87
|
-
"@rspress/core": "^2.0.
|
|
88
|
+
"@rspress/core": "^2.0.16",
|
|
88
89
|
"react": "^19.2.5",
|
|
89
90
|
"react-dom": "^19.2.5"
|
|
90
91
|
},
|
|
@@ -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 {
|