@ciderpress/ui 1.0.0-rc.11 → 1.0.0-rc.14
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/themes/honeycrisp.css +1 -1
- package/dist/node.mjs +719 -26
- package/dist/plugins/mermaid/MermaidRenderer.tsx +2 -2
- package/dist/theme/components/home/hero-banner.tsx +225 -0
- package/dist/theme/components/home/hero-demo.css +492 -61
- package/dist/theme/components/home/hero-demo.test.tsx +159 -0
- package/dist/theme/components/home/hero-demo.tsx +335 -32
- package/dist/theme/components/home/hero.css +142 -9
- package/dist/theme/components/home/hero.test.tsx +105 -0
- package/dist/theme/components/home/hero.tsx +201 -4
- package/dist/theme/components/home/home-visual.css +98 -0
- package/dist/theme/components/home/home-visual.test.tsx +27 -0
- package/dist/theme/components/home/home-visual.tsx +20 -13
- package/dist/theme/components/home/layout.tsx +125 -1
- package/dist/theme/components/home/split.css +2 -2
- package/dist/theme/components/home/tabs.css +1 -1
- package/dist/theme/components/home/trust-strip.css +12 -0
- package/dist/theme/components/home/trust-strip.test.tsx +33 -0
- package/dist/theme/components/home/trust-strip.tsx +76 -15
- package/dist/theme/components/nav/layout.tsx +10 -14
- package/dist/theme/components/nav/nav-logo.tsx +2 -3
- package/dist/theme/components/seo-head-data.test.ts +196 -0
- package/dist/theme/components/seo-head-data.ts +490 -0
- package/dist/theme/components/seo-head.tsx +102 -0
- package/dist/theme/components/shared/IssueLinkIcon.tsx +42 -0
- package/dist/theme/components/shared/issue-link.css +74 -0
- package/dist/theme/components/sidebar/sidebar-filter.test.ts +49 -9
- package/dist/theme/components/sidebar/sidebar-filter.ts +24 -4
- package/dist/theme/index.tsx +9 -1
- package/dist/theme/lib/rich-text-parse.ts +0 -2
- package/dist/theme/lib/seo-url.ts +28 -0
- package/dist/theme/lib/theme-favicon.ts +0 -3
- package/dist/theme/styles/themes/honeycrisp.css +20 -20
- package/package.json +23 -21
- package/src/theme/components/home/hero-banner.tsx +225 -0
- package/src/theme/components/home/hero-demo.css +492 -61
- package/src/theme/components/home/hero-demo.test.tsx +159 -0
- package/src/theme/components/home/hero-demo.tsx +335 -32
- package/src/theme/components/home/hero.css +142 -9
- package/src/theme/components/home/hero.test.tsx +105 -0
- package/src/theme/components/home/hero.tsx +201 -4
- package/src/theme/components/home/home-visual.css +98 -0
- package/src/theme/components/home/home-visual.test.tsx +27 -0
- package/src/theme/components/home/home-visual.tsx +20 -13
- package/src/theme/components/home/layout.tsx +125 -1
- package/src/theme/components/home/split.css +2 -2
- package/src/theme/components/home/tabs.css +1 -1
- package/src/theme/components/home/trust-strip.css +12 -0
- package/src/theme/components/home/trust-strip.test.tsx +33 -0
- package/src/theme/components/home/trust-strip.tsx +76 -15
- package/src/theme/components/nav/layout.tsx +10 -14
- package/src/theme/components/nav/nav-logo.tsx +2 -3
- package/src/theme/components/seo-head-data.test.ts +196 -0
- package/src/theme/components/seo-head-data.ts +490 -0
- package/src/theme/components/seo-head.tsx +102 -0
- package/src/theme/components/shared/IssueLinkIcon.tsx +42 -0
- package/src/theme/components/shared/issue-link.css +74 -0
- package/src/theme/components/sidebar/sidebar-filter.test.ts +49 -9
- package/src/theme/components/sidebar/sidebar-filter.ts +24 -4
- package/src/theme/index.tsx +9 -1
- package/src/theme/lib/rich-text-parse.ts +0 -2
- package/src/theme/lib/seo-url.ts +28 -0
- package/src/theme/lib/theme-favicon.ts +0 -3
- package/src/theme/styles/themes/honeycrisp.css +20 -20
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import type { BannerFn, CiderpressConfig, ImageSource, LogoContext } from '@ciderpress/config'
|
|
2
|
+
// oxlint-disable-next-line import/no-unresolved -- alias provided by createRspressConfig's resolve.alias
|
|
3
|
+
import userConfigModule from '@ciderpress/internal/user-config'
|
|
4
|
+
import { BRAND_COLORS, DEFAULT_THEME_NAME } from '@ciderpress/theme'
|
|
5
|
+
import React, { useEffect, useState } from 'react'
|
|
6
|
+
|
|
7
|
+
import { withMountBase } from '../../lib/with-mount-base.ts'
|
|
8
|
+
|
|
9
|
+
const COLOR_VARS = Object.freeze({
|
|
10
|
+
brand: '--rp-c-brand',
|
|
11
|
+
brandHover: '--rp-c-brand-dark',
|
|
12
|
+
brandSoft: '--rp-c-brand-tint',
|
|
13
|
+
bg: '--rp-c-bg',
|
|
14
|
+
text: '--rp-c-text-1',
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const DEFAULT_BRAND = BRAND_COLORS[DEFAULT_THEME_NAME]
|
|
18
|
+
const FALLBACK_COLORS = Object.freeze({
|
|
19
|
+
brand: DEFAULT_BRAND.primary,
|
|
20
|
+
brandHover: DEFAULT_BRAND.hover,
|
|
21
|
+
brandSoft: DEFAULT_BRAND.soft,
|
|
22
|
+
bg: '#ffffff',
|
|
23
|
+
text: '#1f2937',
|
|
24
|
+
})
|
|
25
|
+
const FALLBACK_THEME: LogoContext = Object.freeze({
|
|
26
|
+
name: DEFAULT_THEME_NAME,
|
|
27
|
+
variant: 'dark',
|
|
28
|
+
isDark: true,
|
|
29
|
+
colors: FALLBACK_COLORS,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resolve and render a function-valued `brand.banner` with the live theme context.
|
|
34
|
+
*
|
|
35
|
+
* @returns Theme-aware banner content, an image, or null.
|
|
36
|
+
*/
|
|
37
|
+
export function HeroBanner(): React.ReactElement | null {
|
|
38
|
+
const banner = readBannerConfig(userConfigModule)
|
|
39
|
+
const theme = useThemeContext(typeof banner === 'function')
|
|
40
|
+
|
|
41
|
+
if (typeof banner !== 'function') {
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return <>{renderBanner({ banner, theme })}</>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface RenderBannerParams {
|
|
49
|
+
readonly banner: BannerFn
|
|
50
|
+
readonly theme: LogoContext
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Invoke a banner function and normalize image returns into elements.
|
|
55
|
+
*
|
|
56
|
+
* @private
|
|
57
|
+
* @param params - Banner function and active theme context.
|
|
58
|
+
* @returns Renderable banner content.
|
|
59
|
+
*/
|
|
60
|
+
function renderBanner(params: RenderBannerParams): React.ReactNode {
|
|
61
|
+
const result = params.banner({ theme: params.theme })
|
|
62
|
+
if (typeof result === 'string') {
|
|
63
|
+
return <img src={withMountBase(result)} alt="" />
|
|
64
|
+
}
|
|
65
|
+
if (isImageSource(result)) {
|
|
66
|
+
return (
|
|
67
|
+
<img
|
|
68
|
+
src={withMountBase(result.src)}
|
|
69
|
+
alt={result.alt ?? ''}
|
|
70
|
+
width={result.width}
|
|
71
|
+
height={result.height}
|
|
72
|
+
/>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
return result as React.ReactNode
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Read a function-valued banner from the bundled user config.
|
|
80
|
+
*
|
|
81
|
+
* @private
|
|
82
|
+
* @param mod - User config module or its default export.
|
|
83
|
+
* @returns Banner function when configured.
|
|
84
|
+
*/
|
|
85
|
+
function readBannerConfig(mod: unknown): BannerFn | undefined {
|
|
86
|
+
if (mod === null || mod === undefined) {
|
|
87
|
+
return undefined
|
|
88
|
+
}
|
|
89
|
+
const record = mod as Record<string, unknown>
|
|
90
|
+
const candidate = readConfigExport({ mod, record })
|
|
91
|
+
const brand = candidate.brand
|
|
92
|
+
if (brand === undefined || typeof brand.banner !== 'function') {
|
|
93
|
+
return undefined
|
|
94
|
+
}
|
|
95
|
+
return brand.banner
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface ReadConfigExportParams {
|
|
99
|
+
readonly mod: unknown
|
|
100
|
+
readonly record: Record<string, unknown>
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Normalize default and direct user-config module exports.
|
|
105
|
+
*
|
|
106
|
+
* @private
|
|
107
|
+
* @param params - Raw module and record view.
|
|
108
|
+
* @returns Partial Ciderpress config.
|
|
109
|
+
*/
|
|
110
|
+
function readConfigExport(params: ReadConfigExportParams): Partial<CiderpressConfig> {
|
|
111
|
+
if (params.record.default !== null && params.record.default !== undefined) {
|
|
112
|
+
return params.record.default as Partial<CiderpressConfig>
|
|
113
|
+
}
|
|
114
|
+
return params.mod as Partial<CiderpressConfig>
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Subscribe to theme changes only when a runtime banner needs them.
|
|
119
|
+
*
|
|
120
|
+
* @private
|
|
121
|
+
* @param enabled - Whether the banner is function-valued.
|
|
122
|
+
* @returns Server-safe default context followed by the live browser context.
|
|
123
|
+
*/
|
|
124
|
+
function useThemeContext(enabled: boolean): LogoContext {
|
|
125
|
+
const [theme, setTheme] = useState<LogoContext>(FALLBACK_THEME)
|
|
126
|
+
|
|
127
|
+
useEffect(() => {
|
|
128
|
+
if (!enabled) {
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
const html = globalThis.document.documentElement
|
|
132
|
+
function update(): void {
|
|
133
|
+
return setTheme(readThemeContext(html))
|
|
134
|
+
}
|
|
135
|
+
update()
|
|
136
|
+
|
|
137
|
+
const observer = new MutationObserver(update)
|
|
138
|
+
observer.observe(html, {
|
|
139
|
+
attributes: true,
|
|
140
|
+
attributeFilter: ['data-cp-theme', 'data-cp-variant', 'class', 'style'],
|
|
141
|
+
})
|
|
142
|
+
return () => observer.disconnect()
|
|
143
|
+
}, [enabled])
|
|
144
|
+
|
|
145
|
+
return theme
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Distinguish a structured image source from a React element.
|
|
150
|
+
*
|
|
151
|
+
* @private
|
|
152
|
+
* @param value - Banner return value.
|
|
153
|
+
* @returns Whether the value is an image source object.
|
|
154
|
+
*/
|
|
155
|
+
function isImageSource(value: unknown): value is Exclude<ImageSource, string> {
|
|
156
|
+
if (value === null || typeof value !== 'object') {
|
|
157
|
+
return false
|
|
158
|
+
}
|
|
159
|
+
const record = value as Record<string, unknown>
|
|
160
|
+
if ('$$typeof' in record) {
|
|
161
|
+
return false
|
|
162
|
+
}
|
|
163
|
+
return typeof record.src === 'string'
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Build the live theme context passed to `brand.banner`.
|
|
168
|
+
*
|
|
169
|
+
* @private
|
|
170
|
+
* @param html - Document root element.
|
|
171
|
+
* @returns Current theme snapshot.
|
|
172
|
+
*/
|
|
173
|
+
function readThemeContext(html: HTMLElement): LogoContext {
|
|
174
|
+
const variant = readVariant(html)
|
|
175
|
+
const name = readThemeName(html)
|
|
176
|
+
const styles = globalThis.window.getComputedStyle(html)
|
|
177
|
+
function read(cssVar: string, fallback: string): string {
|
|
178
|
+
const raw = styles.getPropertyValue(cssVar).trim()
|
|
179
|
+
if (raw.length === 0) {
|
|
180
|
+
return fallback
|
|
181
|
+
}
|
|
182
|
+
return raw
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
name,
|
|
187
|
+
variant,
|
|
188
|
+
isDark: variant === 'dark',
|
|
189
|
+
colors: {
|
|
190
|
+
brand: read(COLOR_VARS.brand, FALLBACK_COLORS.brand),
|
|
191
|
+
brandHover: read(COLOR_VARS.brandHover, FALLBACK_COLORS.brandHover),
|
|
192
|
+
brandSoft: read(COLOR_VARS.brandSoft, FALLBACK_COLORS.brandSoft),
|
|
193
|
+
bg: read(COLOR_VARS.bg, FALLBACK_COLORS.bg),
|
|
194
|
+
text: read(COLOR_VARS.text, FALLBACK_COLORS.text),
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Read the active color variant from the document root.
|
|
201
|
+
*
|
|
202
|
+
* @private
|
|
203
|
+
* @param html - Document root element.
|
|
204
|
+
* @returns Active variant.
|
|
205
|
+
*/
|
|
206
|
+
function readVariant(html: HTMLElement): 'light' | 'dark' {
|
|
207
|
+
if (html.dataset.cpVariant === 'light') {
|
|
208
|
+
return 'light'
|
|
209
|
+
}
|
|
210
|
+
return 'dark'
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Read the active theme name from the document root.
|
|
215
|
+
*
|
|
216
|
+
* @private
|
|
217
|
+
* @param html - Document root element.
|
|
218
|
+
* @returns Active theme name.
|
|
219
|
+
*/
|
|
220
|
+
function readThemeName(html: HTMLElement): string {
|
|
221
|
+
if (typeof html.dataset.cpTheme === 'string') {
|
|
222
|
+
return html.dataset.cpTheme
|
|
223
|
+
}
|
|
224
|
+
return DEFAULT_THEME_NAME
|
|
225
|
+
}
|