@moldea.ai/website-ui 1.6.1 → 1.7.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.
- package/README.md +14 -4
- package/package.json +4 -1
- package/src/components/breadcrumbs/breadcrumbs.component.astro +10 -2
- package/src/components/code-block/code-block.component.astro +4 -1
- package/src/components/code-copy-controls/code-copy-controls.component.astro +111 -0
- package/src/components/documentation-shell/documentation-outline.component.astro +5 -1
- package/src/components/documentation-shell/documentation-shell.component.astro +54 -10
- package/src/components/local-search/local-search.component.astro +35 -10
- package/src/components/site-header/site-header.component.astro +21 -5
- package/src/styles.css +16 -0
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ The package owns the reusable design tokens, global website primitives, interact
|
|
|
7
7
|
## Install after release
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
pnpm add @moldea.ai/website-ui@1.
|
|
10
|
+
pnpm add @moldea.ai/website-ui@1.7.0
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
The package currently supports Astro `7.2.2` and Tailwind CSS `4.3.3` exactly. Import the shared stylesheet once from the website's global stylesheet:
|
|
@@ -25,7 +25,7 @@ Tailwind scans the shipped Astro components through the package stylesheet's exp
|
|
|
25
25
|
- Use `HeroBackdrop` inside a `relative overflow-hidden` section, with foreground content positioned `relative`. It owns the static, theme-aware radial/grid background shared with the Skill website. Do not duplicate its gradients in page markup.
|
|
26
26
|
- Section introductions may place the title on the left and a concise explanation on the right at `lg`, stacking on mobile. Alternate this with compact stacked introductions when the content warrants it; do not force every section into the same composition.
|
|
27
27
|
- Demonstrations should show recognizable files, one visible connection or mismatch, and a concise outcome. Compose `FilePreview`, `ConnectionLabel`, `ResultSummary`, `StatusBadge`, `CodeBlock`, and `Dialog`. Keep the essential result visible without interaction; dialogs contain optional evidence. Use `Accordion` when several examples share a section.
|
|
28
|
-
- Preserve semantic inline code for filenames, variables, operations, and package names using `inline-code`; use `InlineBrandText` for prose containing the product name. Keep code wrapping and interaction feedback in the shared policies below.
|
|
28
|
+
- Preserve semantic inline code for filenames, variables, operations, and package names using `inline-code`; use `InlineBrandText` for prose containing the product name. Breadcrumbs, documentation navigation, the site header, and local-search results apply the same product-name treatment to their string labels. Keep code wrapping and interaction feedback in the shared policies below.
|
|
29
29
|
- Verify compositions at 320px through desktop, in both themes, with keyboard focus and reduced motion. Flatten unnecessary mobile panels and preserve legible filenames, labels, and status text. Use icons and explicit text together rather than relying on color alone.
|
|
30
30
|
|
|
31
31
|
## Surface-aware selection
|
|
@@ -75,6 +75,7 @@ Every component has a dedicated public subpath:
|
|
|
75
75
|
- `@moldea.ai/website-ui/brand-logo`
|
|
76
76
|
- `@moldea.ai/website-ui/breadcrumbs`
|
|
77
77
|
- `@moldea.ai/website-ui/code-block`
|
|
78
|
+
- `@moldea.ai/website-ui/code-copy-controls`
|
|
78
79
|
- `@moldea.ai/website-ui/connection-label`
|
|
79
80
|
- `@moldea.ai/website-ui/documentation-shell`
|
|
80
81
|
- `@moldea.ai/website-ui/dialog`
|
|
@@ -94,7 +95,7 @@ Every component has a dedicated public subpath:
|
|
|
94
95
|
- `@moldea.ai/website-ui/theme-bootstrap`
|
|
95
96
|
- `@moldea.ai/website-ui/theme-control`
|
|
96
97
|
|
|
97
|
-
`ThemeBootstrap` belongs in the document head before rendered content. Pass the same app-owned storage key to `ThemeControl`. Mount `NavigationProgress` once near the start of the document body in websites that use Astro's `ClientRouter`; it reports client navigation preparation without taking ownership of the app's layout. `BrandLogo` receives app-owned asset paths and labels rather than embedding one site's identity. `LocalSearch` receives app-owned copy, routes, and the generated index URL.
|
|
98
|
+
`ThemeBootstrap` belongs in the document head before rendered content. Pass the same app-owned storage key to `ThemeControl`. Mount `NavigationProgress` once near the start of the document body in websites that use Astro's `ClientRouter`; it reports client navigation preparation without taking ownership of the app's layout. Mount `CodeCopyControls` once in the body to add copy actions to eligible code blocks on direct loads and client navigation. `BrandLogo` receives app-owned asset paths and labels rather than embedding one site's identity. `LocalSearch` receives app-owned copy, routes, and the generated index URL.
|
|
98
99
|
|
|
99
100
|
`SiteHeader`, `SiteFooter`, and `DocumentationShell` own responsive structure while consumers retain navigation data, accessible labels, copy, branding, actions, and page content. `TabbedPanels` keeps every panel readable without JavaScript and adds WAI-ARIA tab behavior, including Arrow Left, Arrow Right, Home, and End, after enhancement. `StatusBadge` exposes semantic tones and border treatments without defining domain status mappings.
|
|
100
101
|
|
|
@@ -163,7 +164,7 @@ The compiled `markdown` entry renders sanitized documents and fragments with sta
|
|
|
163
164
|
|
|
164
165
|
### Code and text wrapping
|
|
165
166
|
|
|
166
|
-
`CodeBlock` receives literal `source`, optional `language`, and `variant="panel"` (default) or `variant="plain"` for use inside another surface. It owns compact typography, syntax highlighting, keyboard scrolling, and light/dark presentation. Pass raw code rather than constructing a Markdown fence; embedded fences and HTML remain literal source. `renderCodeBlock` from the public `markdown` subpath exposes the same rendering for non-component consumers.
|
|
167
|
+
`CodeBlock` receives literal `source`, optional `language`, `copyable`, and `variant="panel"` (default) or `variant="plain"` for use inside another surface. It owns compact typography, syntax highlighting, keyboard scrolling, and light/dark presentation. Pass raw code rather than constructing a Markdown fence; embedded fences and HTML remain literal source. `renderCodeBlock` from the public `markdown` subpath exposes the same rendering for non-component consumers.
|
|
167
168
|
|
|
168
169
|
```astro
|
|
169
170
|
---
|
|
@@ -179,10 +180,19 @@ import CodeBlock from '@moldea.ai/website-ui/code-block';
|
|
|
179
180
|
language="sh"
|
|
180
181
|
variant="plain"
|
|
181
182
|
/>
|
|
183
|
+
<CodeBlock
|
|
184
|
+
source="Illustrative excerpt"
|
|
185
|
+
language="text"
|
|
186
|
+
copyable={false}
|
|
187
|
+
/>
|
|
182
188
|
```
|
|
183
189
|
|
|
184
190
|
Code preserves its source line breaks and scrolls horizontally when needed. This includes JSON, YAML, shell commands, diffs, and Markdown source. Unlabelled fences also preserve lines, since they may contain code or aligned file trees. Only fences explicitly labelled `text`, `txt`, or `plaintext` wrap long lines while retaining their original line breaks. Use these labels for prose, not as a shortcut to force code to fit.
|
|
185
191
|
|
|
192
|
+
With `CodeCopyControls` mounted, `CodeBlock` is copyable by default. Set `copyable={false}` for an illustrative or incomplete block that does not need a copy action. For rendered Markdown and hand-authored code, place `data-code-copy="false"` on the `pre` element or an owning container. An opted-out ancestor disables every code block inside it, while neighboring blocks remain eligible. Opted-out blocks have no toolbar or extra focus stop and remain selectable.
|
|
193
|
+
|
|
194
|
+
The control copies the complete displayed code text, including indentation and blank lines, only after a visitor activates its button. Success is announced after the browser accepts the write. When clipboard access is unavailable or denied, the code stays selectable and the control explains how to copy it manually. The button keeps the stable accessible name “Copy code.”
|
|
195
|
+
|
|
186
196
|
Markdown and literal code renderers apply this policy through `styles.css`. Rendered code blocks are named, keyboard-focusable regions with visible focus indicators. Hand-authored `<pre>` elements use the shared `code-block` class, `tabindex="0"`, `role="region"`, and a descriptive `aria-label`; add `data-code-language="text"` only for plain text. Do not add page-wide wrapping overrides to code, diagnostics, or replay content.
|
|
187
197
|
|
|
188
198
|
### Optional detail dialogs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moldea.ai/website-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Shared Astro foundations for moldea public websites.",
|
|
5
5
|
"homepage": "https://github.com/moldea-ai/packages/tree/main/projects/website-ui#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -65,6 +65,9 @@
|
|
|
65
65
|
"./code-block": {
|
|
66
66
|
"import": "./src/components/code-block/code-block.component.astro"
|
|
67
67
|
},
|
|
68
|
+
"./code-copy-controls": {
|
|
69
|
+
"import": "./src/components/code-copy-controls/code-copy-controls.component.astro"
|
|
70
|
+
},
|
|
68
71
|
"./connection-label": {
|
|
69
72
|
"import": "./src/components/connection-label/connection-label.component.astro"
|
|
70
73
|
},
|
|
@@ -3,6 +3,8 @@ import { ChevronRight } from '@lucide/astro';
|
|
|
3
3
|
|
|
4
4
|
import { withBase } from '../../site/index.ts';
|
|
5
5
|
|
|
6
|
+
import InlineBrandText from '../inline-brand-text/inline-brand-text.component.astro';
|
|
7
|
+
|
|
6
8
|
export interface IBreadcrumb {
|
|
7
9
|
href?: string;
|
|
8
10
|
label: string;
|
|
@@ -35,14 +37,20 @@ const { basePath = import.meta.env.BASE_URL, items } = Astro.props;
|
|
|
35
37
|
class="plain-link"
|
|
36
38
|
href={withBase(item.href, basePath)}
|
|
37
39
|
>
|
|
38
|
-
|
|
40
|
+
<InlineBrandText
|
|
41
|
+
text={item.label}
|
|
42
|
+
variant="compact"
|
|
43
|
+
/>
|
|
39
44
|
</a>
|
|
40
45
|
) : (
|
|
41
46
|
<span
|
|
42
47
|
class="min-w-0 font-medium text-foreground"
|
|
43
48
|
aria-current="page"
|
|
44
49
|
>
|
|
45
|
-
|
|
50
|
+
<InlineBrandText
|
|
51
|
+
text={item.label}
|
|
52
|
+
variant="compact"
|
|
53
|
+
/>
|
|
46
54
|
</span>
|
|
47
55
|
)}
|
|
48
56
|
</li>
|
|
@@ -3,15 +3,18 @@ import { renderCodeBlock } from '@moldea.ai/website-ui/markdown';
|
|
|
3
3
|
|
|
4
4
|
// highlighted source, with a panel by default or a flat surface for composition inside another panel
|
|
5
5
|
export interface Props {
|
|
6
|
+
copyable?: boolean;
|
|
6
7
|
source: string;
|
|
7
8
|
language?: string;
|
|
8
9
|
variant?: 'panel' | 'plain';
|
|
9
10
|
}
|
|
10
|
-
const { source, language, variant = 'panel' } = Astro.props;
|
|
11
|
+
const { copyable = true, source, language, variant = 'panel' } = Astro.props;
|
|
11
12
|
const html = await renderCodeBlock(source, language);
|
|
12
13
|
---
|
|
13
14
|
|
|
14
15
|
<div
|
|
16
|
+
data-code-block
|
|
17
|
+
data-code-copy={copyable ? undefined : 'false'}
|
|
15
18
|
class:list={[
|
|
16
19
|
'prose-moldea min-w-0 text-sm [&_pre]:m-0 [&_pre]:text-xs [&_pre]:leading-6',
|
|
17
20
|
variant === 'panel'
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Copy } from '@lucide/astro';
|
|
3
|
+
|
|
4
|
+
import ActionButton from '../action-button/action-button.component.astro';
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<template data-code-copy-controls-template>
|
|
8
|
+
<div
|
|
9
|
+
class="code-copy-toolbar flex min-h-8 min-w-0 flex-wrap items-center justify-end gap-x-2 gap-y-1"
|
|
10
|
+
data-code-copy-toolbar
|
|
11
|
+
>
|
|
12
|
+
<span
|
|
13
|
+
class="min-w-0 text-xs text-muted-foreground empty:hidden"
|
|
14
|
+
aria-live="polite"
|
|
15
|
+
aria-atomic="true"
|
|
16
|
+
data-code-copy-feedback
|
|
17
|
+
></span>
|
|
18
|
+
<ActionButton
|
|
19
|
+
class="ms-auto"
|
|
20
|
+
size="sm"
|
|
21
|
+
variant="ghost"
|
|
22
|
+
aria-label="Copy code"
|
|
23
|
+
data-code-copy-button
|
|
24
|
+
>
|
|
25
|
+
<Copy
|
|
26
|
+
class="size-3.5"
|
|
27
|
+
aria-hidden="true"
|
|
28
|
+
/>
|
|
29
|
+
<span aria-hidden="true">Copy</span>
|
|
30
|
+
</ActionButton>
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script>
|
|
35
|
+
const OPT_OUT_SELECTOR = '[data-code-copy="false"]';
|
|
36
|
+
|
|
37
|
+
/** Reports clipboard results beside the control without changing its accessible name. */
|
|
38
|
+
const setCopyFeedback = (feedback: HTMLElement, message: string): void => {
|
|
39
|
+
feedback.textContent = message;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Copies one block after an explicit button activation and reports the resolved outcome. */
|
|
43
|
+
const initializeCopyButton = (
|
|
44
|
+
button: HTMLButtonElement,
|
|
45
|
+
feedback: HTMLElement,
|
|
46
|
+
code: HTMLElement,
|
|
47
|
+
): void => {
|
|
48
|
+
let latestRequest: symbol | undefined;
|
|
49
|
+
|
|
50
|
+
button.addEventListener('click', async () => {
|
|
51
|
+
const request = Symbol();
|
|
52
|
+
latestRequest = request;
|
|
53
|
+
setCopyFeedback(feedback, '');
|
|
54
|
+
|
|
55
|
+
const writeText = navigator.clipboard?.writeText;
|
|
56
|
+
|
|
57
|
+
if (writeText === undefined) {
|
|
58
|
+
setCopyFeedback(feedback, 'Copy is unavailable. Select the code, then copy it manually.');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
await writeText.call(navigator.clipboard, code.textContent ?? '');
|
|
64
|
+
|
|
65
|
+
if (latestRequest === request && button.isConnected) {
|
|
66
|
+
setCopyFeedback(feedback, 'Copied.');
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
if (latestRequest === request && button.isConnected) {
|
|
70
|
+
setCopyFeedback(feedback, 'Copy failed. Select the code, then copy it manually.');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Adds one copy toolbar to every eligible code block in the current document. */
|
|
77
|
+
const initializeCodeCopyControls = (): void => {
|
|
78
|
+
const template = document.querySelector<HTMLTemplateElement>(
|
|
79
|
+
'[data-code-copy-controls-template]',
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (template === null) return;
|
|
83
|
+
|
|
84
|
+
for (const pre of document.querySelectorAll<HTMLPreElement>('pre')) {
|
|
85
|
+
const code = pre.firstElementChild;
|
|
86
|
+
|
|
87
|
+
if (
|
|
88
|
+
!(code instanceof HTMLElement) ||
|
|
89
|
+
code.tagName !== 'CODE' ||
|
|
90
|
+
pre.dataset.codeCopyEnhanced === 'true' ||
|
|
91
|
+
pre.closest(OPT_OUT_SELECTOR) !== null
|
|
92
|
+
) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const fragment = template.content.cloneNode(true) as DocumentFragment;
|
|
97
|
+
const toolbar = fragment.querySelector<HTMLElement>('[data-code-copy-toolbar]');
|
|
98
|
+
const button = fragment.querySelector<HTMLButtonElement>('[data-code-copy-button]');
|
|
99
|
+
const feedback = fragment.querySelector<HTMLElement>('[data-code-copy-feedback]');
|
|
100
|
+
|
|
101
|
+
if (toolbar === null || button === null || feedback === null) continue;
|
|
102
|
+
|
|
103
|
+
initializeCopyButton(button, feedback, code);
|
|
104
|
+
pre.dataset.codeCopyEnhanced = 'true';
|
|
105
|
+
pre.before(toolbar);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
document.addEventListener('astro:page-load', initializeCodeCopyControls);
|
|
110
|
+
initializeCodeCopyControls();
|
|
111
|
+
</script>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { IDocumentationOutlineHeading } from './documentation-shell.component.astro';
|
|
3
|
+
import InlineBrandText from '../inline-brand-text/inline-brand-text.component.astro';
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
5
6
|
headings: readonly IDocumentationOutlineHeading[];
|
|
@@ -14,7 +15,10 @@ const { headings } = Astro.props;
|
|
|
14
15
|
class:list={['plain-link text-sm leading-5', heading.depth === 3 && 'ps-3']}
|
|
15
16
|
href={`#${heading.id}`}
|
|
16
17
|
>
|
|
17
|
-
|
|
18
|
+
<InlineBrandText
|
|
19
|
+
text={heading.text}
|
|
20
|
+
variant="compact"
|
|
21
|
+
/>
|
|
18
22
|
</a>
|
|
19
23
|
))
|
|
20
24
|
}
|
|
@@ -3,6 +3,7 @@ import { ArrowLeft, ArrowRight, ChevronDown, Menu } from '@lucide/astro';
|
|
|
3
3
|
|
|
4
4
|
import { withBase } from '../../site/index.ts';
|
|
5
5
|
import Breadcrumbs, { type IBreadcrumb } from '../breadcrumbs/breadcrumbs.component.astro';
|
|
6
|
+
import InlineBrandText from '../inline-brand-text/inline-brand-text.component.astro';
|
|
6
7
|
|
|
7
8
|
import DocumentationOutline from './documentation-outline.component.astro';
|
|
8
9
|
|
|
@@ -77,7 +78,10 @@ const contentGridClass = hasNavigation
|
|
|
77
78
|
class="size-4"
|
|
78
79
|
aria-hidden="true"
|
|
79
80
|
/>
|
|
80
|
-
|
|
81
|
+
<InlineBrandText
|
|
82
|
+
text={mobileNavigationLabel}
|
|
83
|
+
variant="compact"
|
|
84
|
+
/>
|
|
81
85
|
</summary>
|
|
82
86
|
<nav
|
|
83
87
|
class="grid gap-5 border-t border-border p-4"
|
|
@@ -85,7 +89,14 @@ const contentGridClass = hasNavigation
|
|
|
85
89
|
>
|
|
86
90
|
{navigationGroups.map((group) => (
|
|
87
91
|
<div>
|
|
88
|
-
{group.label &&
|
|
92
|
+
{group.label && (
|
|
93
|
+
<p class="eyebrow mb-2 px-2">
|
|
94
|
+
<InlineBrandText
|
|
95
|
+
text={group.label}
|
|
96
|
+
variant="compact"
|
|
97
|
+
/>
|
|
98
|
+
</p>
|
|
99
|
+
)}
|
|
89
100
|
<div class="grid gap-1">
|
|
90
101
|
{group.items.map((item) => (
|
|
91
102
|
<a
|
|
@@ -93,7 +104,10 @@ const contentGridClass = hasNavigation
|
|
|
93
104
|
href={withBase(item.href, basePath)}
|
|
94
105
|
aria-current={item.href === currentRoute ? 'page' : undefined}
|
|
95
106
|
>
|
|
96
|
-
|
|
107
|
+
<InlineBrandText
|
|
108
|
+
text={item.label}
|
|
109
|
+
variant="compact"
|
|
110
|
+
/>
|
|
97
111
|
</a>
|
|
98
112
|
))}
|
|
99
113
|
</div>
|
|
@@ -108,7 +122,10 @@ const contentGridClass = hasNavigation
|
|
|
108
122
|
hasOutline && (
|
|
109
123
|
<details class="surface-card group sticky top-16 z-40 mt-3 xl:hidden">
|
|
110
124
|
<summary class="flex min-h-12 cursor-pointer list-none items-center justify-between gap-3 px-4 font-semibold outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset [&::-webkit-details-marker]:hidden">
|
|
111
|
-
|
|
125
|
+
<InlineBrandText
|
|
126
|
+
text={outlineLabel}
|
|
127
|
+
variant="compact"
|
|
128
|
+
/>
|
|
112
129
|
<ChevronDown
|
|
113
130
|
class="size-4"
|
|
114
131
|
aria-hidden="true"
|
|
@@ -135,7 +152,14 @@ const contentGridClass = hasNavigation
|
|
|
135
152
|
>
|
|
136
153
|
{navigationGroups.map((group) => (
|
|
137
154
|
<div>
|
|
138
|
-
{group.label &&
|
|
155
|
+
{group.label && (
|
|
156
|
+
<p class="eyebrow mb-2 px-2">
|
|
157
|
+
<InlineBrandText
|
|
158
|
+
text={group.label}
|
|
159
|
+
variant="compact"
|
|
160
|
+
/>
|
|
161
|
+
</p>
|
|
162
|
+
)}
|
|
139
163
|
<div class="grid gap-1">
|
|
140
164
|
{group.items.map((item) => (
|
|
141
165
|
<a
|
|
@@ -143,7 +167,10 @@ const contentGridClass = hasNavigation
|
|
|
143
167
|
href={withBase(item.href, basePath)}
|
|
144
168
|
aria-current={item.href === currentRoute ? 'page' : undefined}
|
|
145
169
|
>
|
|
146
|
-
|
|
170
|
+
<InlineBrandText
|
|
171
|
+
text={item.label}
|
|
172
|
+
variant="compact"
|
|
173
|
+
/>
|
|
147
174
|
</a>
|
|
148
175
|
))}
|
|
149
176
|
</div>
|
|
@@ -178,7 +205,12 @@ const contentGridClass = hasNavigation
|
|
|
178
205
|
<span class="block text-xs font-bold tracking-wide text-muted-foreground uppercase">
|
|
179
206
|
Previous
|
|
180
207
|
</span>
|
|
181
|
-
<span class="mt-1 block font-semibold">
|
|
208
|
+
<span class="mt-1 block font-semibold">
|
|
209
|
+
<InlineBrandText
|
|
210
|
+
text={previousPage.label}
|
|
211
|
+
variant="compact"
|
|
212
|
+
/>
|
|
213
|
+
</span>
|
|
182
214
|
</span>
|
|
183
215
|
</a>
|
|
184
216
|
) : (
|
|
@@ -193,7 +225,12 @@ const contentGridClass = hasNavigation
|
|
|
193
225
|
<span class="block text-xs font-bold tracking-wide text-muted-foreground uppercase">
|
|
194
226
|
Next
|
|
195
227
|
</span>
|
|
196
|
-
<span class="mt-1 block font-semibold">
|
|
228
|
+
<span class="mt-1 block font-semibold">
|
|
229
|
+
<InlineBrandText
|
|
230
|
+
text={nextPage.label}
|
|
231
|
+
variant="compact"
|
|
232
|
+
/>
|
|
233
|
+
</span>
|
|
197
234
|
</span>
|
|
198
235
|
<ArrowRight
|
|
199
236
|
class="size-4 shrink-0"
|
|
@@ -210,7 +247,12 @@ const contentGridClass = hasNavigation
|
|
|
210
247
|
(hasOutline || hasNavigation) && (
|
|
211
248
|
<aside class="hidden xl:block">
|
|
212
249
|
<div class="sticky top-24 max-h-[calc(100vh-7rem)] overflow-y-auto pe-2">
|
|
213
|
-
<p class="eyebrow mb-4">
|
|
250
|
+
<p class="eyebrow mb-4">
|
|
251
|
+
<InlineBrandText
|
|
252
|
+
text={outlineLabel}
|
|
253
|
+
variant="compact"
|
|
254
|
+
/>
|
|
255
|
+
</p>
|
|
214
256
|
{hasOutline ? (
|
|
215
257
|
<nav
|
|
216
258
|
class="grid gap-2 border-s ps-4"
|
|
@@ -219,7 +261,9 @@ const contentGridClass = hasNavigation
|
|
|
219
261
|
<DocumentationOutline headings={headings} />
|
|
220
262
|
</nav>
|
|
221
263
|
) : (
|
|
222
|
-
<p class="text-sm text-muted-foreground">
|
|
264
|
+
<p class="text-sm text-muted-foreground">
|
|
265
|
+
<InlineBrandText text={outlineEmptyLabel} />
|
|
266
|
+
</p>
|
|
223
267
|
)}
|
|
224
268
|
</div>
|
|
225
269
|
</aside>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Search } from '@lucide/astro';
|
|
3
3
|
|
|
4
4
|
import ActionButton from '../action-button/action-button.component.astro';
|
|
5
|
+
import InlineBrandText from '../inline-brand-text/inline-brand-text.component.astro';
|
|
5
6
|
|
|
6
7
|
export interface Props {
|
|
7
8
|
action: string;
|
|
@@ -74,7 +75,10 @@ const {
|
|
|
74
75
|
size="lg"
|
|
75
76
|
type="submit"
|
|
76
77
|
>
|
|
77
|
-
|
|
78
|
+
<InlineBrandText
|
|
79
|
+
text={buttonLabel}
|
|
80
|
+
variant="compact"
|
|
81
|
+
/>
|
|
78
82
|
</ActionButton>
|
|
79
83
|
</div>
|
|
80
84
|
</form>
|
|
@@ -84,7 +88,7 @@ const {
|
|
|
84
88
|
aria-live="polite"
|
|
85
89
|
data-search-status
|
|
86
90
|
>
|
|
87
|
-
{initialPrompt}
|
|
91
|
+
<InlineBrandText text={initialPrompt} />
|
|
88
92
|
</p>
|
|
89
93
|
<ol
|
|
90
94
|
class="mt-8 grid gap-4"
|
|
@@ -100,6 +104,24 @@ const {
|
|
|
100
104
|
type ISearchDocument,
|
|
101
105
|
} from '../../search/index.ts';
|
|
102
106
|
|
|
107
|
+
/** Replaces text with escaped text and semantic code nodes for every product-name match. */
|
|
108
|
+
const renderInlineBrandText = (element: HTMLElement, text: string): void => {
|
|
109
|
+
const content = document.createDocumentFragment();
|
|
110
|
+
|
|
111
|
+
for (const segment of text.split(/(\bmoldea\b)/giu)) {
|
|
112
|
+
if (/^moldea$/iu.test(segment)) {
|
|
113
|
+
const code = document.createElement('code');
|
|
114
|
+
code.className = 'inline-code tracking-normal';
|
|
115
|
+
code.textContent = 'moldea';
|
|
116
|
+
content.append(code);
|
|
117
|
+
} else {
|
|
118
|
+
content.append(document.createTextNode(segment));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
element.replaceChildren(content);
|
|
123
|
+
};
|
|
124
|
+
|
|
103
125
|
const createSearchResult = (searchDocument: ISearchDocument): HTMLLIElement => {
|
|
104
126
|
const item = document.createElement('li');
|
|
105
127
|
const link = document.createElement('a');
|
|
@@ -110,9 +132,9 @@ const {
|
|
|
110
132
|
link.className = 'block rounded-md';
|
|
111
133
|
link.href = searchDocument.url;
|
|
112
134
|
title.className = 'text-xl font-semibold tracking-tight';
|
|
113
|
-
title
|
|
135
|
+
renderInlineBrandText(title, searchDocument.title);
|
|
114
136
|
excerpt.className = 'mt-2 leading-6 text-muted-foreground';
|
|
115
|
-
excerpt
|
|
137
|
+
renderInlineBrandText(excerpt, searchDocument.description);
|
|
116
138
|
link.append(title, excerpt);
|
|
117
139
|
item.append(link);
|
|
118
140
|
|
|
@@ -157,11 +179,11 @@ const {
|
|
|
157
179
|
results.replaceChildren();
|
|
158
180
|
|
|
159
181
|
if (!query.trim()) {
|
|
160
|
-
status
|
|
182
|
+
renderInlineBrandText(status, root.dataset.initialPrompt ?? '');
|
|
161
183
|
return;
|
|
162
184
|
}
|
|
163
185
|
|
|
164
|
-
status
|
|
186
|
+
renderInlineBrandText(status, root.dataset.loadingMessage ?? '');
|
|
165
187
|
|
|
166
188
|
const pendingDocuments = (documentsPromise ??= loadSearchDocuments(searchIndexUrl));
|
|
167
189
|
const documents = await pendingDocuments;
|
|
@@ -170,15 +192,18 @@ const {
|
|
|
170
192
|
if (latestRequest !== request || !root.isConnected) return;
|
|
171
193
|
|
|
172
194
|
if (documents === null) {
|
|
173
|
-
status
|
|
195
|
+
renderInlineBrandText(status, root.dataset.failureMessage ?? '');
|
|
174
196
|
return;
|
|
175
197
|
}
|
|
176
198
|
|
|
177
199
|
const matches = searchDocuments(query, documents);
|
|
178
200
|
results.replaceChildren(...matches.map(createSearchResult));
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
201
|
+
renderInlineBrandText(
|
|
202
|
+
status,
|
|
203
|
+
matches.length
|
|
204
|
+
? `${matches.length} result${matches.length === 1 ? '' : 's'} for “${query}”.`
|
|
205
|
+
: (root.dataset.noResultsMessage ?? ''),
|
|
206
|
+
);
|
|
182
207
|
};
|
|
183
208
|
|
|
184
209
|
form.addEventListener('submit', (event) => {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { GitFork, Menu, Search } from '@lucide/astro';
|
|
3
3
|
|
|
4
4
|
import ActionLink from '../action-link/action-link.component.astro';
|
|
5
|
+
import InlineBrandText from '../inline-brand-text/inline-brand-text.component.astro';
|
|
5
6
|
import ThemeControl from '../theme-control/theme-control.component.astro';
|
|
6
7
|
|
|
7
8
|
export interface ISiteHeaderNavigationItem {
|
|
@@ -77,17 +78,26 @@ const responsiveClasses = breakpointClasses[breakpoint];
|
|
|
77
78
|
class="xl:hidden"
|
|
78
79
|
aria-hidden="true"
|
|
79
80
|
>
|
|
80
|
-
|
|
81
|
+
<InlineBrandText
|
|
82
|
+
text={compactLabel}
|
|
83
|
+
variant="compact"
|
|
84
|
+
/>
|
|
81
85
|
</span>
|
|
82
86
|
<span
|
|
83
87
|
class="hidden xl:inline"
|
|
84
88
|
aria-hidden="true"
|
|
85
89
|
>
|
|
86
|
-
|
|
90
|
+
<InlineBrandText
|
|
91
|
+
text={label}
|
|
92
|
+
variant="compact"
|
|
93
|
+
/>
|
|
87
94
|
</span>
|
|
88
95
|
</>
|
|
89
96
|
) : (
|
|
90
|
-
|
|
97
|
+
<InlineBrandText
|
|
98
|
+
text={label}
|
|
99
|
+
variant="compact"
|
|
100
|
+
/>
|
|
91
101
|
)}
|
|
92
102
|
</a>
|
|
93
103
|
))
|
|
@@ -150,7 +160,10 @@ const responsiveClasses = breakpointClasses[breakpoint];
|
|
|
150
160
|
href={href}
|
|
151
161
|
aria-current={isActive ? 'page' : undefined}
|
|
152
162
|
>
|
|
153
|
-
|
|
163
|
+
<InlineBrandText
|
|
164
|
+
text={label}
|
|
165
|
+
variant="compact"
|
|
166
|
+
/>
|
|
154
167
|
</a>
|
|
155
168
|
))
|
|
156
169
|
}
|
|
@@ -167,7 +180,10 @@ const responsiveClasses = breakpointClasses[breakpoint];
|
|
|
167
180
|
target="_blank"
|
|
168
181
|
rel="noopener noreferrer"
|
|
169
182
|
>
|
|
170
|
-
|
|
183
|
+
<InlineBrandText
|
|
184
|
+
text={sourceLabel}
|
|
185
|
+
variant="compact"
|
|
186
|
+
/>
|
|
171
187
|
</a>
|
|
172
188
|
<slot name="mobile-actions" />
|
|
173
189
|
</nav>
|
package/src/styles.css
CHANGED
|
@@ -202,6 +202,22 @@
|
|
|
202
202
|
@apply max-w-full overflow-x-auto rounded-xl border bg-code p-5 text-sm leading-6 text-code-foreground shadow-inset;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
.code-copy-toolbar {
|
|
206
|
+
@apply mb-2;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.prose-moldea .code-copy-toolbar {
|
|
210
|
+
@apply mt-5;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.prose-moldea[data-code-block] .code-copy-toolbar {
|
|
214
|
+
@apply mt-0;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.code-copy-toolbar + pre {
|
|
218
|
+
margin-top: 0;
|
|
219
|
+
}
|
|
220
|
+
|
|
205
221
|
/* Preserve code, structured output, and file-tree alignment by default. */
|
|
206
222
|
.code-block,
|
|
207
223
|
.prose-moldea pre {
|