coherent-docs-theme 1.0.0

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 (40) hide show
  1. package/README.md +314 -0
  2. package/assets/gameface-ui-header-dark.svg +5 -0
  3. package/assets/gameface-ui-header-light.svg +5 -0
  4. package/components/Api.astro +29 -0
  5. package/components/BeforeAfter.astro +124 -0
  6. package/components/Details.astro +37 -0
  7. package/components/Enhancement.astro +29 -0
  8. package/components/Feature.astro +29 -0
  9. package/components/Figure.astro +107 -0
  10. package/components/Fix.astro +29 -0
  11. package/components/GallerySlider.astro +260 -0
  12. package/components/If.astro +6 -0
  13. package/components/IfEnv.astro +5 -0
  14. package/components/IfNot.astro +6 -0
  15. package/components/IncludeSnippets.astro +13 -0
  16. package/components/Internal.astro +3 -0
  17. package/components/ProductName.astro +13 -0
  18. package/components/Release.astro +68 -0
  19. package/components/SideBarWithDropdown.astro +8 -0
  20. package/components/Typeref.astro +35 -0
  21. package/index.ts +152 -0
  22. package/internal/overrideComponents.ts +29 -0
  23. package/internal/themeConfig.ts +21 -0
  24. package/internal-components/ChangelogRow.astro +39 -0
  25. package/internal-components/NavLinks.astro +253 -0
  26. package/internal-components/ProgressIndicator.astro +53 -0
  27. package/overrides/Footer.astro +103 -0
  28. package/overrides/Header.astro +91 -0
  29. package/overrides/Search.astro +234 -0
  30. package/overrides/ThemeSelect.astro +218 -0
  31. package/package.json +46 -0
  32. package/remark-directives/if.ts +51 -0
  33. package/remark-directives/includeSnippets.ts +120 -0
  34. package/remark-directives/index.ts +13 -0
  35. package/remark-directives/internal.ts +20 -0
  36. package/remark-directives/release.ts +29 -0
  37. package/styles.css +452 -0
  38. package/utils/changelogSideBar.ts +105 -0
  39. package/utils/changelogSideBarMultipleDocs.ts +119 -0
  40. package/utils/coherentReleases.ts +62 -0
@@ -0,0 +1,29 @@
1
+ ---
2
+ import ChangelogRow, {
3
+ type ChangelogProps,
4
+ } from "../internal-components/ChangelogRow.astro";
5
+
6
+ interface Props extends ChangelogProps {}
7
+
8
+ const { engine, description } = Astro.props;
9
+ ---
10
+
11
+ {
12
+ Astro.slots.has("default") ? (
13
+ <ChangelogRow
14
+ badgeText="Fix"
15
+ badgeVariant="danger"
16
+ engine={engine}
17
+ description={description}
18
+ >
19
+ <slot />
20
+ </ChangelogRow>
21
+ ) : (
22
+ <ChangelogRow
23
+ badgeText="Fix"
24
+ badgeVariant="danger"
25
+ engine={engine}
26
+ description={description}
27
+ />
28
+ )
29
+ }
@@ -0,0 +1,260 @@
1
+ ---
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { Icon } from "@astrojs/starlight/components";
5
+ import { Image } from "astro:assets";
6
+ import type { ImageMetadata } from "astro";
7
+
8
+ type CustomSlide = {
9
+ src: string | ImageMetadata;
10
+ link?: string;
11
+ description?: string;
12
+ alt?: string;
13
+ };
14
+
15
+ type GalleryItem = string | ImageMetadata | CustomSlide;
16
+
17
+ interface Props {
18
+ dir?: string;
19
+ images?: GalleryItem[];
20
+ width?: string;
21
+ height?: string;
22
+ autoSlide?: number;
23
+ }
24
+
25
+ let {
26
+ dir,
27
+ images = [],
28
+ width = "100%",
29
+ height = "300px",
30
+ autoSlide = 0,
31
+ } = Astro.props;
32
+
33
+ let slides: CustomSlide[] = [];
34
+
35
+ if (dir) {
36
+ const publicPath = path.join(process.cwd(), "public", dir);
37
+ try {
38
+ if (fs.existsSync(publicPath)) {
39
+ const files = fs.readdirSync(publicPath);
40
+ const imageFiles = files.filter((file) =>
41
+ /\.(png|jpe?g|gif|svg|webp)$/i.test(file),
42
+ );
43
+ slides.push(
44
+ ...imageFiles.map((file) => ({ src: `/${dir}/${file}` }))
45
+ );
46
+ }
47
+ } catch (e) {
48
+ console.error(`[Gallery] Error while reading the dir: ${publicPath}`, e);
49
+ }
50
+ }
51
+
52
+ images.forEach((item: any) => {
53
+ if (typeof item === "string") {
54
+ slides.push({ src: item });
55
+ } else if (
56
+ item.format !== undefined &&
57
+ item.width !== undefined &&
58
+ item.height !== undefined &&
59
+ item.description === undefined
60
+ ) {
61
+ slides.push({ src: item as ImageMetadata });
62
+ } else {
63
+ slides.push(item as CustomSlide);
64
+ }
65
+ });
66
+
67
+ if (slides.length === 0) {
68
+ console.warn("[Gallery] No images found.");
69
+ }
70
+ ---
71
+
72
+ <gallery-slider data-auto-slide={autoSlide}>
73
+ <div class="slide-window" style={`width: ${width}; height: ${height};`}>
74
+ <ul class="slides-list">
75
+ {
76
+ slides.map((slide) => {
77
+ const isLocal = typeof slide.src === "object";
78
+ const altText = slide.alt || "Gallery slide";
79
+
80
+ const imgContent = isLocal ? (
81
+ <Image src={slide.src as ImageMetadata} alt={altText} class="slide-img" />
82
+ ) : (
83
+ <img src={slide.src as string} alt={altText} class="slide-img" />
84
+ );
85
+
86
+ const slideBody = slide.link ? (
87
+ <a href={slide.link} target="_blank" rel="noopener noreferrer" class="slide-link">
88
+ {imgContent}
89
+ </a>
90
+ ) : (
91
+ imgContent
92
+ );
93
+
94
+ return (
95
+ <li class="slide">
96
+ {slideBody}
97
+
98
+ {slide.description && (
99
+ <div class="slide-caption">
100
+ <p>{slide.description}</p>
101
+ </div>
102
+ )}
103
+ </li>
104
+ );
105
+ })
106
+ }
107
+ </ul>
108
+
109
+ <button class="nav left" aria-label="Previous image">
110
+ <Icon name="left-arrow" size="2rem" />
111
+ </button>
112
+ <button class="nav right" aria-label="Next image">
113
+ <Icon name="right-arrow" size="2rem" />
114
+ </button>
115
+ </div>
116
+ </gallery-slider>
117
+
118
+ <style>
119
+ gallery-slider {
120
+ display: block;
121
+ margin: 2rem auto;
122
+ max-width: 100%;
123
+ }
124
+
125
+ .slide-window {
126
+ position: relative;
127
+ overflow: hidden;
128
+ border-radius: 8px;
129
+ box-shadow: var(--sl-shadow-md);
130
+ background-color: var(--sl-color-gray-6);
131
+ }
132
+
133
+ .slides-list {
134
+ display: flex;
135
+ width: 100%;
136
+ height: 100%;
137
+ margin: 0 !important;
138
+ padding: 0 !important;
139
+ list-style: none !important;
140
+ transition: transform 0.5s ease-in-out;
141
+ }
142
+
143
+ .slide {
144
+ flex: 0 0 100%;
145
+ height: 100%;
146
+ position: relative;
147
+ }
148
+
149
+ .slide-link {
150
+ display: block;
151
+ width: 100%;
152
+ height: 100%;
153
+ }
154
+
155
+ .slide-img {
156
+ width: 100%;
157
+ height: 100%;
158
+ object-fit: contain;
159
+ display: block;
160
+ user-select: none;
161
+ margin-bottom: 0 !important;
162
+ }
163
+
164
+ .slide-caption {
165
+ position: absolute;
166
+ bottom: 0;
167
+ left: 0;
168
+ width: 100%;
169
+ background-color: rgba(0, 0, 0, 0.75);
170
+ color: white;
171
+ padding: 1rem 1.5rem;
172
+ text-align: center;
173
+ box-sizing: border-box;
174
+ pointer-events: none;
175
+ }
176
+
177
+ .slide-caption p {
178
+ margin: 0 !important;
179
+ font-size: 0.95rem;
180
+ line-height: 1.4;
181
+ }
182
+
183
+ .nav {
184
+ position: absolute;
185
+ top: 50%;
186
+ transform: translateY(-50%);
187
+ background: rgba(0, 0, 0, 0.5);
188
+ color: white;
189
+ border: none;
190
+ border-radius: 50%;
191
+ width: 3rem;
192
+ height: 3rem;
193
+ display: flex;
194
+ align-items: center;
195
+ justify-content: center;
196
+ cursor: pointer;
197
+ z-index: 10;
198
+ transition: background 0.2s ease;
199
+ }
200
+
201
+ .nav:hover {
202
+ background: rgba(0, 0, 0, 0.9);
203
+ }
204
+
205
+ .nav.left { left: 1rem; }
206
+ .nav.right { right: 1rem; }
207
+ </style>
208
+
209
+ <script>
210
+ class GallerySlider extends HTMLElement {
211
+ currentSlide: number = 0;
212
+ slidesList: HTMLElement | undefined;
213
+ slides: NodeListOf<Element> | undefined;
214
+ totalSlides: number = 0;
215
+ autoSlide: number = 0;
216
+ interval: NodeJS.Timeout | null = null;
217
+
218
+ connectedCallback() {
219
+ this.currentSlide = 0;
220
+ this.slidesList = this.querySelector(".slides-list") as HTMLElement;
221
+ this.slides = this.querySelectorAll(".slide");
222
+ this.totalSlides = this.slides.length;
223
+ this.autoSlide = parseInt(this.dataset.autoSlide || "0", 10);
224
+ this.interval = null;
225
+
226
+ if (this.totalSlides === 0) return;
227
+
228
+ this.querySelector(".left")?.addEventListener("click", () => this.move("back"));
229
+ this.querySelector(".right")?.addEventListener("click", () => this.move("forward"));
230
+
231
+ this.resetAutoSlide();
232
+ }
233
+
234
+ move(direction: "back" | "forward") {
235
+ if (direction === "back") {
236
+ this.currentSlide = (this.currentSlide - 1 + this.totalSlides) % this.totalSlides;
237
+ } else {
238
+ this.currentSlide = (this.currentSlide + 1) % this.totalSlides;
239
+ }
240
+ this.updateDOM();
241
+ this.resetAutoSlide();
242
+ }
243
+
244
+ updateDOM() {
245
+ if (this.slidesList)
246
+ this.slidesList.style.transform = `translateX(-${this.currentSlide * 100}%)`;
247
+ }
248
+
249
+ resetAutoSlide() {
250
+ if (this.autoSlide > 0) {
251
+ if (this.interval) clearInterval(this.interval);
252
+ this.interval = setInterval(() => this.move("forward"), this.autoSlide) as any;
253
+ }
254
+ }
255
+ }
256
+
257
+ if (!customElements.get("gallery-slider")) {
258
+ customElements.define("gallery-slider", GallerySlider);
259
+ }
260
+ </script>
@@ -0,0 +1,6 @@
1
+ ---
2
+ interface Props {
3
+ product?: "gameface" | "prysm";
4
+ type?: "cxx" | "unity3d";
5
+ }
6
+ ---
@@ -0,0 +1,5 @@
1
+ ---
2
+ interface Props {
3
+ env: 'development' | 'production' | string;
4
+ }
5
+ ---
@@ -0,0 +1,6 @@
1
+ ---
2
+ interface Props {
3
+ product?: "gameface" | "prysm";
4
+ type?: "cxx" | "unity3d";
5
+ }
6
+ ---
@@ -0,0 +1,13 @@
1
+ ---
2
+ interface Props {
3
+ release: string;
4
+ tag:
5
+ | "changelog"
6
+ | "rendering"
7
+ | "content_development"
8
+ | "migration"
9
+ | "core"
10
+ | "unreal_engine";
11
+ noBullets?: boolean;
12
+ }
13
+ ---
@@ -0,0 +1,3 @@
1
+ ---
2
+ interface Props {}
3
+ ---
@@ -0,0 +1,13 @@
1
+ ---
2
+ const rawProduct = import.meta.env.DOCS_PRODUCT || 'gameface';
3
+
4
+ function humanize(str: string) {
5
+ if (!str) return '';
6
+ const cleanStr = str.replace(/[-_]/g, ' ');
7
+ return cleanStr.charAt(0).toUpperCase() + cleanStr.slice(1);
8
+ }
9
+
10
+ const productName = humanize(rawProduct);
11
+ ---
12
+
13
+ {productName}
@@ -0,0 +1,68 @@
1
+ ---
2
+ interface Props {
3
+ date: string;
4
+ version: string
5
+ }
6
+
7
+ const { date } = Astro.props;
8
+ ---
9
+
10
+ <div class="release-container">
11
+ <hr />
12
+ <div class="release-date">
13
+ <a
14
+ href="https://coherent-labs.com/get-in-touch/"
15
+ target="_blank"
16
+ rel="noopener noreferrer"
17
+ >
18
+ <span>Released {date}</span>
19
+ </a>
20
+ </div>
21
+
22
+ <div class="table-wrapper">
23
+ <table class="table table-striped">
24
+ <tbody>
25
+ <slot />
26
+ </tbody>
27
+ </table>
28
+ </div>
29
+ </div>
30
+
31
+ <style>
32
+ .release-container {
33
+ margin-bottom: 2rem;
34
+ }
35
+
36
+ .release-date {
37
+ margin-bottom: 1.5rem;
38
+ font-size: 0.95rem;
39
+ }
40
+
41
+ .table-wrapper {
42
+ overflow-x: auto;
43
+ }
44
+
45
+ .table-wrapper :global(.table-striped td:first-child),
46
+ .table-wrapper :global(.table-striped th:first-child) {
47
+ padding-inline-start: 1rem !important;
48
+ }
49
+
50
+ .table-wrapper :global(.table-striped td:last-child),
51
+ .table-wrapper :global(.table-striped th:last-child) {
52
+ padding-inline-end: 1rem !important;
53
+ }
54
+
55
+ .table-wrapper :global(.table-striped td) {
56
+ padding-block: 0.75rem;
57
+ vertical-align: middle;
58
+ }
59
+
60
+ .table-striped {
61
+ width: 100%;
62
+ border-collapse: collapse;
63
+ }
64
+
65
+ .table-striped :global(tbody tr:nth-of-type(odd)) {
66
+ background-color: var(--sl-color-gray-6);
67
+ }
68
+ </style>
@@ -0,0 +1,8 @@
1
+ ---
2
+ import Default from '@astrojs/starlight/components/Sidebar.astro';
3
+ import TopicsDropdown from 'starlight-sidebar-topics-dropdown/TopicsDropdown.astro';
4
+ ---
5
+
6
+ <TopicsDropdown />
7
+
8
+ <Default><slot /></Default>
@@ -0,0 +1,35 @@
1
+ ---
2
+ interface Props {
3
+ title: string;
4
+ nativeRef?: string;
5
+ unityRef?: string;
6
+ }
7
+
8
+ const { title, nativeRef = "", unityRef = "" } = Astro.props;
9
+
10
+ const currentType = import.meta.env.DOCS_TYPE || "";
11
+
12
+ let resolvedPath = "";
13
+
14
+ if (currentType === "cxx" && nativeRef !== "") {
15
+ resolvedPath = nativeRef;
16
+ } else if (currentType === "unity3d" && unityRef !== "") {
17
+ resolvedPath = unityRef;
18
+ }
19
+
20
+ if (resolvedPath.endsWith(".mdx")) {
21
+ resolvedPath = resolvedPath.replace(/\.mdx$/, "");
22
+ }
23
+
24
+ if (resolvedPath.endsWith(".md")) {
25
+ resolvedPath = resolvedPath.replace(/\.md$/, "");
26
+ }
27
+ ---
28
+
29
+ {
30
+ resolvedPath !== "" ? (
31
+ <a href={resolvedPath}>{title}</a>
32
+ ) : (
33
+ <Fragment>{title}</Fragment>
34
+ )
35
+ }
package/index.ts ADDED
@@ -0,0 +1,152 @@
1
+ import type { StarlightPlugin } from '@astrojs/starlight/types';
2
+ import { overrideComponents } from './internal/overrideComponents';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import starlightHeadingBadges from 'starlight-heading-badges';
6
+ import generateChangelogMultiple from './utils/changelogSideBarMultipleDocs';
7
+ import generateChangelog from './utils/changelogSideBar';
8
+ import type { CoherentThemeOptions } from './internal/themeConfig';
9
+ import { fileURLToPath } from 'url';
10
+ import { directives } from './remark-directives';
11
+ import { getSortedCoherentReleases } from './utils/coherentReleases';
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+ const defaultHeaderLinks = [
16
+ { href: 'https://docs.coherent-labs.com/cpp-gameface', label: 'Gameface' },
17
+ { href: 'https://docs.coherent-labs.com/cpp-prysm', label: 'Prysm' },
18
+ { href: 'https://starter.coherent-labs.com/', label: 'UI Starter Guide' },
19
+ { href: 'https://frontend-tools.coherent-labs.com', label: 'UI Tools' },
20
+ { href: 'https://gameface-ui.coherent-labs.com', label: 'Gameface UI' },
21
+ { href: 'https://coherent-labs.com/Documentation/ExporterLTS/', label: 'Adobe CC Tools' },
22
+ ];
23
+ const defaultMergeIndex = [
24
+ {
25
+ bundlePath: "https://gameface-ui.coherent-labs.com/pagefind",
26
+ indexWeight: 0.5,
27
+ mergeFilter: {
28
+ resource: "Gameface UI"
29
+ }
30
+ },
31
+ {
32
+ bundlePath: "https://frontend-tools.coherent-labs.com/pagefind",
33
+ indexWeight: 0.5,
34
+ mergeFilter: {
35
+ resource: "UI Tools"
36
+ }
37
+ }
38
+ ]
39
+
40
+ export default function coherentThemePlugin(options: CoherentThemeOptions = { documentationSearchTag: '' }): StarlightPlugin[] {
41
+ if (!options?.documentationSearchTag) {
42
+ throw new Error('Coherent docs theme plugin requires "documentationSearchTag"!')
43
+ }
44
+
45
+ let navLinks = defaultHeaderLinks;
46
+ for (const link of options.navLinks ?? []) {
47
+ navLinks.push(link)
48
+ }
49
+
50
+ const {
51
+ showPageProgress = false,
52
+ disableDefaultLogo = false,
53
+ } = options;
54
+
55
+ const corePlugin: StarlightPlugin = {
56
+ name: 'coherent-docs-theme',
57
+ hooks: {
58
+ 'config:setup'({ config, logger, updateConfig, addIntegration }) {
59
+ logger.info('Initializing Coherent Theme...');
60
+
61
+ addIntegration({
62
+ name: 'coherent-docs-theme-integration',
63
+ hooks: {
64
+ 'astro:config:setup': ({ updateConfig }) => {
65
+ updateConfig({
66
+ markdown: {
67
+ remarkPlugins: [...directives],
68
+ },
69
+ });
70
+ }
71
+ }
72
+ });
73
+
74
+ process.env.COHERENT_THEME_CONFIG = JSON.stringify({ showPageProgress, navLinks, documentationSearchTag: options.documentationSearchTag });
75
+
76
+ const configUpdates: any = {
77
+ customCss: [...(config.customCss ?? []), 'coherent-docs-theme/styles'],
78
+ components: overrideComponents(
79
+ config,
80
+ ['Header', 'ThemeSelect', 'Footer', 'Search'],
81
+ logger,
82
+ ),
83
+ head: config.head || [],
84
+ };
85
+ if (!disableDefaultLogo && !config.logo) {
86
+ configUpdates.logo = {
87
+ dark: path.join(__dirname, 'assets/gameface-ui-header-dark.svg'),
88
+ light: path.join(__dirname, 'assets/gameface-ui-header-light.svg'),
89
+ replacesTitle: options.replacesTitle ?? true,
90
+ };
91
+ }
92
+
93
+ configUpdates.head.push({
94
+ tag: 'meta',
95
+ attrs: {
96
+ 'data-pagefind-filter': 'resource[content]',
97
+ content: options.documentationSearchTag
98
+ }
99
+ })
100
+
101
+ configUpdates.pagefind = {
102
+ indexWeight: 2,
103
+ mergeIndex: defaultMergeIndex.filter(merge => {
104
+ const resource = merge.mergeFilter.resource;
105
+ return resource !== options.documentationSearchTag;
106
+ })
107
+ };
108
+
109
+ updateConfig(configUpdates);
110
+ },
111
+ },
112
+ };
113
+
114
+ const plugins = [
115
+ starlightHeadingBadges(),
116
+ corePlugin,
117
+ ];
118
+
119
+ return plugins
120
+ }
121
+
122
+ export function generateVersion(version: string, link?: string) {
123
+ const config: { label: string; link: string; attrs?: { target: string }; badge?: { text: string; variant: "note" | "danger" | "success" | "caution" | "tip" | "default" } } = {
124
+ label: 'Version:',
125
+ link: '/',
126
+ badge: {
127
+ text: `${version}`,
128
+ variant: 'tip',
129
+ },
130
+ }
131
+
132
+ if (link) {
133
+ config.link = link;
134
+ config.attrs = { target: '_blank' };
135
+ }
136
+
137
+ return config;
138
+ }
139
+
140
+ export async function generateVersionWithPackageJSON(packagePath: string, link?: string) {
141
+ if (!fs.existsSync(packagePath)) throw new Error(`Version not found in ${packagePath}`);
142
+ const packageContent = fs.readFileSync(packagePath, 'utf-8');
143
+ const packageJson = JSON.parse(packageContent);
144
+ const version = packageJson?.version;
145
+ if (!version) throw new Error(`Version not defined in ${packagePath}`);
146
+
147
+ return generateVersion(version, link);
148
+ }
149
+
150
+ export const generateMultipleDocsChangelog = generateChangelogMultiple;
151
+ export const generateDocsChangelog = generateChangelog;
152
+ export const getCoherentReleases = getSortedCoherentReleases;
@@ -0,0 +1,29 @@
1
+ import type { HookParameters } from '@astrojs/starlight/types'
2
+ import type { AstroIntegrationLogger } from 'astro'
3
+
4
+ export function overrideComponents(
5
+ starlightConfig: StarlightUserConfig,
6
+ overrides: ComponentOverride[],
7
+ logger: AstroIntegrationLogger,
8
+ ): StarlightUserConfig['components'] {
9
+ const components = { ...starlightConfig.components }
10
+
11
+ for (const override of overrides) {
12
+ const name = typeof override === 'string' ? override : override.name
13
+ if (starlightConfig.components?.[name]) {
14
+ logger.info(`Overriding coherent-docs-theme's \`<${name}>\` component with \`${starlightConfig.components?.[name]}\`.`)
15
+ continue
16
+ }
17
+ components[name] = `coherent-docs-theme/overrides/${name}.astro`
18
+ }
19
+
20
+ return components
21
+ }
22
+
23
+ type StarlightUserConfig = HookParameters<'config:setup'>['config']
24
+
25
+ type ComponentOverride =
26
+ | keyof NonNullable<StarlightUserConfig['components']>
27
+ | {
28
+ name: keyof NonNullable<StarlightUserConfig['components']>
29
+ }
@@ -0,0 +1,21 @@
1
+ export interface CoherentThemeOptions {
2
+ documentationSearchTag: string
3
+ showPageProgress?: boolean;
4
+ navLinks?: Array<{ label: string; href: string }>;
5
+ disableDefaultLogo?: boolean;
6
+ replacesTitle?: boolean
7
+ }
8
+
9
+ export default function getThemeConfig(): CoherentThemeOptions {
10
+ let themeConfig = { documentationSearchTag: '', showPageProgress: false, navLinks: [], disableDefaultLogo: false } as CoherentThemeOptions;
11
+
12
+ if (process.env.COHERENT_THEME_CONFIG) {
13
+ try {
14
+ themeConfig = { ...themeConfig, ...JSON.parse(process.env.COHERENT_THEME_CONFIG) };
15
+ } catch (e) {
16
+ console.error("Failed to parse Coherent Theme config");
17
+ }
18
+ }
19
+
20
+ return themeConfig;
21
+ }