specra 0.2.6 → 0.2.8
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/config/specra.config.schema.json +23 -0
- package/config/svelte-config.js +32 -1
- package/dist/category.d.ts +1 -1
- package/dist/category.js +4 -2
- package/dist/components/docs/Breadcrumb.svelte +11 -4
- package/dist/components/docs/Breadcrumb.svelte.d.ts +1 -0
- package/dist/components/docs/CategoryIndex.svelte +11 -2
- package/dist/components/docs/CategoryIndex.svelte.d.ts +1 -0
- package/dist/components/docs/DocLayout.svelte +9 -8
- package/dist/components/docs/DocLayout.svelte.d.ts +1 -0
- package/dist/components/docs/DocNavigation.svelte +10 -3
- package/dist/components/docs/DocNavigation.svelte.d.ts +1 -0
- package/dist/components/docs/Header.svelte +17 -1
- package/dist/components/docs/Header.svelte.d.ts +10 -0
- package/dist/components/docs/MdxContent.svelte +3 -1
- package/dist/components/docs/MobileDocLayout.svelte +5 -1
- package/dist/components/docs/MobileDocLayout.svelte.d.ts +1 -0
- package/dist/components/docs/MobileSidebar.svelte +3 -1
- package/dist/components/docs/MobileSidebar.svelte.d.ts +1 -0
- package/dist/components/docs/MobileSidebarWrapper.svelte +3 -1
- package/dist/components/docs/MobileSidebarWrapper.svelte.d.ts +1 -0
- package/dist/components/docs/ProductSwitcher.svelte +175 -0
- package/dist/components/docs/ProductSwitcher.svelte.d.ts +28 -0
- package/dist/components/docs/SearchModal.svelte +4 -3
- package/dist/components/docs/Sidebar.svelte +3 -1
- package/dist/components/docs/Sidebar.svelte.d.ts +1 -0
- package/dist/components/docs/SidebarMenuItems.svelte +39 -9
- package/dist/components/docs/SidebarMenuItems.svelte.d.ts +1 -0
- package/dist/components/docs/TabGroups.svelte +9 -2
- package/dist/components/docs/TabGroups.svelte.d.ts +1 -0
- package/dist/components/docs/VersionSwitcher.svelte +1 -1
- package/dist/components/docs/api/ApiParams.svelte +94 -36
- package/dist/components/docs/index.d.ts +1 -0
- package/dist/components/docs/index.js +1 -0
- package/dist/components/ui/Button.svelte +30 -11
- package/dist/components/ui/Button.svelte.d.ts +10 -4
- package/dist/config.d.ts +1 -1
- package/dist/config.schema.json +18 -0
- package/dist/config.server.d.ts +35 -7
- package/dist/config.server.js +185 -23
- package/dist/config.types.d.ts +46 -0
- package/dist/mdx-cache.d.ts +3 -6
- package/dist/mdx-cache.js +36 -8
- package/dist/mdx.d.ts +8 -3
- package/dist/mdx.js +117 -10
- package/dist/parsers/openapi-parser.js +6 -1
- package/dist/redirects.d.ts +2 -1
- package/dist/redirects.js +37 -11
- package/package.json +1 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Flat shape (from page components) */
|
|
2
|
+
interface ProductItemFlat {
|
|
3
|
+
slug: string;
|
|
4
|
+
label: string;
|
|
5
|
+
icon?: string;
|
|
6
|
+
badge?: string;
|
|
7
|
+
activeVersion?: string;
|
|
8
|
+
isDefault: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** Nested shape (from SDK getProducts()) */
|
|
11
|
+
interface ProductItemNested {
|
|
12
|
+
slug: string;
|
|
13
|
+
config: {
|
|
14
|
+
label: string;
|
|
15
|
+
icon?: string;
|
|
16
|
+
badge?: string;
|
|
17
|
+
activeVersion?: string;
|
|
18
|
+
};
|
|
19
|
+
isDefault: boolean;
|
|
20
|
+
}
|
|
21
|
+
type ProductInput = ProductItemFlat | ProductItemNested;
|
|
22
|
+
interface Props {
|
|
23
|
+
products: ProductInput[];
|
|
24
|
+
currentProduct?: string;
|
|
25
|
+
}
|
|
26
|
+
declare const ProductSwitcher: import("svelte").Component<Props, {}, "">;
|
|
27
|
+
type ProductSwitcher = ReturnType<typeof ProductSwitcher>;
|
|
28
|
+
export default ProductSwitcher;
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
let inputEl = $state<HTMLInputElement | null>(null);
|
|
29
29
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
30
30
|
|
|
31
|
-
const
|
|
31
|
+
const siteBaseUrl = $derived(config.site?.baseUrl || '/');
|
|
32
|
+
const docsBaseUrl = '/docs';
|
|
32
33
|
|
|
33
34
|
$effect(() => {
|
|
34
35
|
if (isOpen && inputEl) {
|
|
@@ -75,7 +76,7 @@
|
|
|
75
76
|
debounceTimer = setTimeout(async () => {
|
|
76
77
|
try {
|
|
77
78
|
const response = await fetch(
|
|
78
|
-
`${
|
|
79
|
+
`${siteBaseUrl.replace(/\/$/, '')}/api/search?q=${encodeURIComponent(value.trim())}`
|
|
79
80
|
);
|
|
80
81
|
if (response.ok) {
|
|
81
82
|
const data = await response.json();
|
|
@@ -93,7 +94,7 @@
|
|
|
93
94
|
|
|
94
95
|
function navigateToResult(result: SearchResult) {
|
|
95
96
|
const version = result.version || config.site?.activeVersion || 'v1';
|
|
96
|
-
const url = `${
|
|
97
|
+
const url = `${docsBaseUrl}/${version}/${result.slug}`;
|
|
97
98
|
goto(url);
|
|
98
99
|
onClose();
|
|
99
100
|
}
|
|
@@ -26,12 +26,13 @@
|
|
|
26
26
|
interface Props {
|
|
27
27
|
docs: DocItem[];
|
|
28
28
|
version: string;
|
|
29
|
+
product?: string;
|
|
29
30
|
onLinkClick?: () => void;
|
|
30
31
|
config: SpecraConfig;
|
|
31
32
|
activeTabGroup?: string;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
let { docs, version, onLinkClick, config, activeTabGroup }: Props = $props();
|
|
35
|
+
let { docs = [], version, product, onLinkClick, config, activeTabGroup }: Props = $props();
|
|
35
36
|
|
|
36
37
|
let isFlush = $derived(config.navigation?.sidebarStyle === 'flush');
|
|
37
38
|
let containerClass = $derived(
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
<SidebarMenuItems
|
|
54
55
|
{docs}
|
|
55
56
|
{version}
|
|
57
|
+
{product}
|
|
56
58
|
{onLinkClick}
|
|
57
59
|
{config}
|
|
58
60
|
{activeTabGroup}
|
|
@@ -42,14 +42,43 @@
|
|
|
42
42
|
interface Props {
|
|
43
43
|
docs: DocItem[];
|
|
44
44
|
version: string;
|
|
45
|
+
product?: string;
|
|
45
46
|
onLinkClick?: () => void;
|
|
46
47
|
config: SpecraConfig;
|
|
47
48
|
activeTabGroup?: string;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
let { docs, version, onLinkClick, config, activeTabGroup }: Props = $props();
|
|
51
|
+
let { docs = [], version, product, onLinkClick, config, activeTabGroup }: Props = $props();
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
/** URL prefix: /docs/{product}/{version} for named products, /docs/{version} for default */
|
|
54
|
+
let docsBase = $derived(
|
|
55
|
+
product && product !== '_default_'
|
|
56
|
+
? `/docs/${product}/${version}`
|
|
57
|
+
: `/docs/${version}`
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const STORAGE_KEY = 'specra-sidebar-collapsed';
|
|
61
|
+
|
|
62
|
+
function loadCollapsedState(): Record<string, boolean> {
|
|
63
|
+
if (typeof window === 'undefined') return {};
|
|
64
|
+
try {
|
|
65
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
66
|
+
return stored ? JSON.parse(stored) : {};
|
|
67
|
+
} catch {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function saveCollapsedState(state: Record<string, boolean>) {
|
|
73
|
+
if (typeof window === 'undefined') return;
|
|
74
|
+
try {
|
|
75
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
|
76
|
+
} catch {
|
|
77
|
+
// localStorage unavailable
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let collapsed: Record<string, boolean> = $state(loadCollapsedState());
|
|
53
82
|
let pathname = $derived($page.url.pathname.replace(/\/$/, ''));
|
|
54
83
|
|
|
55
84
|
// Filter docs by active tab group if tab groups are configured
|
|
@@ -172,18 +201,19 @@
|
|
|
172
201
|
|
|
173
202
|
function toggleSection(section: string) {
|
|
174
203
|
collapsed = { ...collapsed, [section]: !collapsed[section] };
|
|
204
|
+
saveCollapsedState(collapsed);
|
|
175
205
|
}
|
|
176
206
|
|
|
177
207
|
function isActiveInGroup(group: SidebarGroup): boolean {
|
|
178
208
|
const hasActiveItem = group.items.some(
|
|
179
|
-
(doc) => pathname ===
|
|
209
|
+
(doc) => pathname === `${docsBase}/${doc.slug}`
|
|
180
210
|
);
|
|
181
211
|
if (hasActiveItem) return true;
|
|
182
212
|
return Object.values(group.children).some((child) => isActiveInGroup(child));
|
|
183
213
|
}
|
|
184
214
|
|
|
185
215
|
function getGroupHref(group: SidebarGroup): string {
|
|
186
|
-
let groupHref =
|
|
216
|
+
let groupHref = `${docsBase}/${group.path}`;
|
|
187
217
|
|
|
188
218
|
if (config.features?.i18n) {
|
|
189
219
|
const i18n = config.features.i18n;
|
|
@@ -192,7 +222,7 @@
|
|
|
192
222
|
const potentialLocale = pathParts[3];
|
|
193
223
|
|
|
194
224
|
if (potentialLocale && locales.includes(potentialLocale)) {
|
|
195
|
-
groupHref =
|
|
225
|
+
groupHref = `${docsBase}/${potentialLocale}/${group.path}`;
|
|
196
226
|
}
|
|
197
227
|
}
|
|
198
228
|
|
|
@@ -201,7 +231,7 @@
|
|
|
201
231
|
|
|
202
232
|
function isGroupCollapsed(groupKey: string, group: SidebarGroup): boolean {
|
|
203
233
|
const hasActive = isActiveInGroup(group);
|
|
204
|
-
const isGroupActive = pathname ===
|
|
234
|
+
const isGroupActive = pathname === `${docsBase}/${group.path}`;
|
|
205
235
|
if (hasActive || isGroupActive) return false;
|
|
206
236
|
return collapsed[groupKey] ?? group.defaultCollapsed;
|
|
207
237
|
}
|
|
@@ -240,7 +270,7 @@
|
|
|
240
270
|
{@const hasChildren = sortedChildren.length > 0}
|
|
241
271
|
{@const hasItems = sortedItems.length > 0}
|
|
242
272
|
{@const hasContent = hasChildren || hasItems}
|
|
243
|
-
{@const isGroupActive = pathname ===
|
|
273
|
+
{@const isGroupActive = pathname === `${docsBase}/${group.path}`}
|
|
244
274
|
{@const isCollapsed = isGroupCollapsed(groupKey, group)}
|
|
245
275
|
{@const marginLeft = depth > 0 ? 'ml-4' : ''}
|
|
246
276
|
{@const groupHref = getGroupHref(group)}
|
|
@@ -289,7 +319,7 @@
|
|
|
289
319
|
{#if item.type === 'group'}
|
|
290
320
|
{@render renderGroup(`${groupKey}/${item.key}`, item.group, depth + 1)}
|
|
291
321
|
{:else}
|
|
292
|
-
{@const href =
|
|
322
|
+
{@const href = `${docsBase}/${item.doc.slug}`}
|
|
293
323
|
{@const isActive = pathname === href}
|
|
294
324
|
<a
|
|
295
325
|
{href}
|
|
@@ -316,7 +346,7 @@
|
|
|
316
346
|
<nav class="space-y-1">
|
|
317
347
|
{#if sortedStandalone.length > 0}
|
|
318
348
|
{#each sortedStandalone as doc (doc.slug)}
|
|
319
|
-
{@const href =
|
|
349
|
+
{@const href = `${docsBase}/${doc.slug}`}
|
|
320
350
|
{@const isActive = pathname === href}
|
|
321
351
|
<a
|
|
322
352
|
{href}
|
|
@@ -24,10 +24,17 @@
|
|
|
24
24
|
mobileOnly?: boolean;
|
|
25
25
|
docs?: DocItem[];
|
|
26
26
|
version?: string;
|
|
27
|
+
product?: string;
|
|
27
28
|
flush?: boolean;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
let { tabGroups, activeTabId, onTabChange, mobileOnly = false, docs, version, flush = false }: Props = $props();
|
|
31
|
+
let { tabGroups, activeTabId, onTabChange, mobileOnly = false, docs, version, product, flush = false }: Props = $props();
|
|
32
|
+
|
|
33
|
+
let docsBase = $derived(
|
|
34
|
+
product && product !== '_default_' && version
|
|
35
|
+
? `/docs/${product}/${version}`
|
|
36
|
+
: version ? `/docs/${version}` : '/docs'
|
|
37
|
+
);
|
|
31
38
|
|
|
32
39
|
let dropdownOpen = $state(false);
|
|
33
40
|
|
|
@@ -58,7 +65,7 @@
|
|
|
58
65
|
});
|
|
59
66
|
|
|
60
67
|
if (firstDocInTab) {
|
|
61
|
-
goto(
|
|
68
|
+
goto(`${docsBase}/${firstDocInTab.slug}`);
|
|
62
69
|
}
|
|
63
70
|
}
|
|
64
71
|
}
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
if (versionsMeta && versionsMeta.length > 0) {
|
|
28
28
|
return versionsMeta.filter(v => !v.hidden);
|
|
29
29
|
}
|
|
30
|
-
return versions.map(id => ({ id, label: id }));
|
|
30
|
+
return (versions ?? []).map(id => ({ id, label: id }));
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
// Get current version display label
|
|
@@ -13,62 +13,54 @@
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
let { title = 'Parameters', params }: Props = $props();
|
|
16
|
+
|
|
17
|
+
let filteredParams = $derived(
|
|
18
|
+
Array.isArray(params) ? params.filter(p => p && p.name) : []
|
|
19
|
+
);
|
|
16
20
|
</script>
|
|
17
21
|
|
|
18
|
-
{#if
|
|
22
|
+
{#if filteredParams.length > 0}
|
|
19
23
|
<div class="mb-6">
|
|
20
24
|
<h4 class="text-sm font-semibold text-foreground mb-3">{title}</h4>
|
|
21
|
-
<div class="
|
|
22
|
-
<table
|
|
25
|
+
<div class="specra-params-table">
|
|
26
|
+
<table>
|
|
23
27
|
<thead>
|
|
24
|
-
<tr
|
|
25
|
-
<th
|
|
26
|
-
|
|
27
|
-
</th>
|
|
28
|
-
<th
|
|
29
|
-
|
|
30
|
-
</th>
|
|
31
|
-
<th class="text-left py-2 px-3 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
|
32
|
-
Required
|
|
33
|
-
</th>
|
|
34
|
-
<th class="text-left py-2 px-3 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
|
35
|
-
Default
|
|
36
|
-
</th>
|
|
37
|
-
<th class="text-left py-2 px-3 text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
|
38
|
-
Description
|
|
39
|
-
</th>
|
|
28
|
+
<tr>
|
|
29
|
+
<th>Property</th>
|
|
30
|
+
<th>Type</th>
|
|
31
|
+
<th>Required</th>
|
|
32
|
+
<th>Default</th>
|
|
33
|
+
<th>Description</th>
|
|
40
34
|
</tr>
|
|
41
35
|
</thead>
|
|
42
36
|
<tbody>
|
|
43
|
-
{#each
|
|
44
|
-
<tr
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<td class="py-2.5 px-3">
|
|
48
|
-
<code class="text-sm font-mono text-foreground">{param.name}</code>
|
|
37
|
+
{#each filteredParams as param, index}
|
|
38
|
+
<tr>
|
|
39
|
+
<td>
|
|
40
|
+
<code>{param.name}</code>
|
|
49
41
|
</td>
|
|
50
|
-
<td
|
|
51
|
-
<span class="
|
|
42
|
+
<td>
|
|
43
|
+
<span class="font-mono">{param.type}</span>
|
|
52
44
|
</td>
|
|
53
|
-
<td
|
|
45
|
+
<td>
|
|
54
46
|
{#if param.required}
|
|
55
|
-
<span class="text-
|
|
47
|
+
<span class="text-red-600 dark:text-red-400">Yes</span>
|
|
56
48
|
{:else}
|
|
57
|
-
|
|
49
|
+
No
|
|
58
50
|
{/if}
|
|
59
51
|
</td>
|
|
60
|
-
<td
|
|
52
|
+
<td>
|
|
61
53
|
{#if param.default}
|
|
62
|
-
<code
|
|
54
|
+
<code>{param.default}</code>
|
|
63
55
|
{:else}
|
|
64
|
-
|
|
56
|
+
-
|
|
65
57
|
{/if}
|
|
66
58
|
</td>
|
|
67
|
-
<td
|
|
59
|
+
<td>
|
|
68
60
|
{#if param.description}
|
|
69
|
-
|
|
61
|
+
{param.description}
|
|
70
62
|
{:else}
|
|
71
|
-
|
|
63
|
+
-
|
|
72
64
|
{/if}
|
|
73
65
|
</td>
|
|
74
66
|
</tr>
|
|
@@ -78,3 +70,69 @@
|
|
|
78
70
|
</div>
|
|
79
71
|
</div>
|
|
80
72
|
{/if}
|
|
73
|
+
|
|
74
|
+
<style>
|
|
75
|
+
.specra-params-table table {
|
|
76
|
+
border-collapse: separate;
|
|
77
|
+
border-spacing: 0;
|
|
78
|
+
border: 1px solid var(--border);
|
|
79
|
+
border-radius: 0.75rem;
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
width: max-content;
|
|
82
|
+
max-width: 100%;
|
|
83
|
+
display: block;
|
|
84
|
+
overflow-x: auto;
|
|
85
|
+
-webkit-overflow-scrolling: touch;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.specra-params-table thead {
|
|
89
|
+
background: var(--muted);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.specra-params-table thead th {
|
|
93
|
+
border-bottom: 1px solid var(--border);
|
|
94
|
+
border-right: 1px solid var(--border);
|
|
95
|
+
padding: 0.625rem 1rem;
|
|
96
|
+
font-weight: 600;
|
|
97
|
+
font-size: 0.75rem;
|
|
98
|
+
text-transform: uppercase;
|
|
99
|
+
letter-spacing: 0.05em;
|
|
100
|
+
text-align: left;
|
|
101
|
+
color: var(--muted-foreground);
|
|
102
|
+
white-space: nowrap;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.specra-params-table thead th:last-child {
|
|
106
|
+
border-right: none;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.specra-params-table tbody td {
|
|
110
|
+
border-bottom: 1px solid var(--border);
|
|
111
|
+
border-right: 1px solid var(--border);
|
|
112
|
+
padding: 0.625rem 1rem;
|
|
113
|
+
font-size: 0.875rem;
|
|
114
|
+
color: var(--muted-foreground);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.specra-params-table tbody td:last-child {
|
|
118
|
+
border-right: none;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.specra-params-table tbody tr:last-child td {
|
|
122
|
+
border-bottom: none;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.specra-params-table tbody tr:hover {
|
|
126
|
+
background: var(--muted);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.specra-params-table code {
|
|
130
|
+
font-size: 0.8125rem;
|
|
131
|
+
font-family: var(--font-mono, ui-monospace, monospace);
|
|
132
|
+
color: var(--foreground);
|
|
133
|
+
background: var(--muted);
|
|
134
|
+
padding: 0.125rem 0.375rem;
|
|
135
|
+
border-radius: 0.25rem;
|
|
136
|
+
border: 1px solid var(--border);
|
|
137
|
+
}
|
|
138
|
+
</style>
|
|
@@ -51,6 +51,7 @@ export { default as TimelineItem } from './TimelineItem.svelte';
|
|
|
51
51
|
export { default as Tabs } from './Tabs.svelte';
|
|
52
52
|
export { default as ThemeToggle } from './ThemeToggle.svelte';
|
|
53
53
|
export { default as Tooltip } from './Tooltip.svelte';
|
|
54
|
+
export { default as ProductSwitcher } from './ProductSwitcher.svelte';
|
|
54
55
|
export { default as VersionBanner } from './VersionBanner.svelte';
|
|
55
56
|
export { default as VersionSwitcher } from './VersionSwitcher.svelte';
|
|
56
57
|
export { default as Video } from './Video.svelte';
|
|
@@ -52,6 +52,7 @@ export { default as TimelineItem } from './TimelineItem.svelte';
|
|
|
52
52
|
export { default as Tabs } from './Tabs.svelte';
|
|
53
53
|
export { default as ThemeToggle } from './ThemeToggle.svelte';
|
|
54
54
|
export { default as Tooltip } from './Tooltip.svelte';
|
|
55
|
+
export { default as ProductSwitcher } from './ProductSwitcher.svelte';
|
|
55
56
|
export { default as VersionBanner } from './VersionBanner.svelte';
|
|
56
57
|
export { default as VersionSwitcher } from './VersionSwitcher.svelte';
|
|
57
58
|
export { default as Video } from './Video.svelte';
|
|
@@ -36,23 +36,42 @@
|
|
|
36
36
|
|
|
37
37
|
<script lang="ts">
|
|
38
38
|
import { cn } from '../../utils.js';
|
|
39
|
-
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
39
|
+
import type { HTMLButtonAttributes, HTMLAnchorAttributes } from 'svelte/elements';
|
|
40
40
|
import type { Snippet } from 'svelte';
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
type ButtonProps = HTMLButtonAttributes & {
|
|
43
|
+
href?: never;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type AnchorProps = HTMLAnchorAttributes & {
|
|
47
|
+
href: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type Props = (ButtonProps | AnchorProps) & {
|
|
43
51
|
variant?: ButtonVariants['variant'];
|
|
44
52
|
size?: ButtonVariants['size'];
|
|
45
53
|
class?: string;
|
|
46
54
|
children?: Snippet;
|
|
47
|
-
}
|
|
55
|
+
};
|
|
48
56
|
|
|
49
|
-
let { variant = 'default', size = 'default', class: className, children, ...restProps }: Props = $props();
|
|
57
|
+
let { variant = 'default', size = 'default', class: className, children, href, ...restProps }: Props = $props();
|
|
50
58
|
</script>
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
{#if href}
|
|
61
|
+
<a
|
|
62
|
+
{href}
|
|
63
|
+
data-slot="button"
|
|
64
|
+
class={cn(buttonVariants({ variant, size, className }))}
|
|
65
|
+
{...restProps}
|
|
66
|
+
>
|
|
67
|
+
{@render children?.()}
|
|
68
|
+
</a>
|
|
69
|
+
{:else}
|
|
70
|
+
<button
|
|
71
|
+
data-slot="button"
|
|
72
|
+
class={cn(buttonVariants({ variant, size, className }))}
|
|
73
|
+
{...restProps}
|
|
74
|
+
>
|
|
75
|
+
{@render children?.()}
|
|
76
|
+
</button>
|
|
77
|
+
{/if}
|
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import { type VariantProps } from 'class-variance-authority';
|
|
2
2
|
export declare const buttonVariants: (props?: ({
|
|
3
3
|
variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
|
|
4
|
-
size?: "
|
|
4
|
+
size?: "icon" | "default" | "sm" | "lg" | "icon-sm" | "icon-lg" | null | undefined;
|
|
5
5
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
6
6
|
export type ButtonVariants = VariantProps<typeof buttonVariants>;
|
|
7
|
-
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
7
|
+
import type { HTMLButtonAttributes, HTMLAnchorAttributes } from 'svelte/elements';
|
|
8
8
|
import type { Snippet } from 'svelte';
|
|
9
|
-
|
|
9
|
+
type ButtonProps = HTMLButtonAttributes & {
|
|
10
|
+
href?: never;
|
|
11
|
+
};
|
|
12
|
+
type AnchorProps = HTMLAnchorAttributes & {
|
|
13
|
+
href: string;
|
|
14
|
+
};
|
|
15
|
+
type Props = (ButtonProps | AnchorProps) & {
|
|
10
16
|
variant?: ButtonVariants['variant'];
|
|
11
17
|
size?: ButtonVariants['size'];
|
|
12
18
|
class?: string;
|
|
13
19
|
children?: Snippet;
|
|
14
|
-
}
|
|
20
|
+
};
|
|
15
21
|
declare const Button: import("svelte").Component<Props, {}, "">;
|
|
16
22
|
type Button = ReturnType<typeof Button>;
|
|
17
23
|
export default Button;
|
package/dist/config.d.ts
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* The actual config loading happens on the server and is passed as props
|
|
5
5
|
*/
|
|
6
6
|
export { defaultConfig } from "./config.types";
|
|
7
|
-
export type { SpecraConfig, VersionConfig, BannerConfig } from "./config.types";
|
|
7
|
+
export type { SpecraConfig, VersionConfig, BannerConfig, ProductConfig, Product, DefaultProductConfig } from "./config.types";
|
|
8
8
|
export { getConfig, getConfigValue, loadConfig, processContentWithEnv, replaceEnvVariables, validateConfig, reloadConfig, } from "./config.server";
|
package/dist/config.schema.json
CHANGED
|
@@ -74,6 +74,24 @@
|
|
|
74
74
|
"hideLogo": {
|
|
75
75
|
"type": "boolean",
|
|
76
76
|
"description": "Whether to hide the site logo in the header"
|
|
77
|
+
},
|
|
78
|
+
"defaultProduct": {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"description": "Configuration for the default product in multi-product mode. Falls back to site title and activeVersion if not set.",
|
|
81
|
+
"properties": {
|
|
82
|
+
"label": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"description": "Display name for the default product in the switcher"
|
|
85
|
+
},
|
|
86
|
+
"icon": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
"description": "Icon for the default product (lucide icon name)"
|
|
89
|
+
},
|
|
90
|
+
"activeVersion": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"description": "Override active version for the default product"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
77
95
|
}
|
|
78
96
|
}
|
|
79
97
|
},
|
package/dist/config.server.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SpecraConfig, VersionConfig } from "./config.types";
|
|
1
|
+
import type { SpecraConfig, VersionConfig, ProductConfig, Product } from "./config.types";
|
|
2
2
|
/**
|
|
3
3
|
* Load and parse the Specra configuration file
|
|
4
4
|
* Falls back to default configuration if file doesn't exist or is invalid
|
|
@@ -41,13 +41,13 @@ export declare function getConfig(): SpecraConfig;
|
|
|
41
41
|
* Reload the configuration (useful for development) (SERVER ONLY)
|
|
42
42
|
*/
|
|
43
43
|
export declare function reloadConfig(userConfig: Partial<SpecraConfig>): SpecraConfig;
|
|
44
|
-
export declare function loadVersionConfig(version: string): VersionConfig | null;
|
|
44
|
+
export declare function loadVersionConfig(version: string, product?: string): VersionConfig | null;
|
|
45
45
|
/**
|
|
46
|
-
* Get the effective config for a specific version.
|
|
47
|
-
* Merges
|
|
48
|
-
* If no
|
|
46
|
+
* Get the effective config for a specific version and optional product.
|
|
47
|
+
* Merges in priority order: global ← product ← version.
|
|
48
|
+
* If no overrides exist, returns the global config unchanged.
|
|
49
49
|
*/
|
|
50
|
-
export declare function getEffectiveConfig(version: string): SpecraConfig;
|
|
50
|
+
export declare function getEffectiveConfig(version: string, product?: string): SpecraConfig;
|
|
51
51
|
/**
|
|
52
52
|
* Version metadata for display in the version switcher.
|
|
53
53
|
*/
|
|
@@ -67,7 +67,35 @@ export interface VersionMeta {
|
|
|
67
67
|
* Get metadata for all versions, enriched with _version_.json data.
|
|
68
68
|
* Hidden versions are included but marked — the UI decides whether to show them.
|
|
69
69
|
*/
|
|
70
|
-
export declare function getVersionsMeta(versions: string[]): VersionMeta[];
|
|
70
|
+
export declare function getVersionsMeta(versions: string[], product?: string): VersionMeta[];
|
|
71
|
+
/**
|
|
72
|
+
* Load and parse a _product_.json file for a given product slug.
|
|
73
|
+
* Returns null if the file doesn't exist or is invalid.
|
|
74
|
+
*/
|
|
75
|
+
export declare function loadProductConfig(product: string): ProductConfig | null;
|
|
76
|
+
/**
|
|
77
|
+
* Scan docs/ top-level directories for _product_.json files.
|
|
78
|
+
* Returns the full list of products including the default product.
|
|
79
|
+
*
|
|
80
|
+
* Detection logic:
|
|
81
|
+
* 1. Single readdir + stat calls — no recursive walks
|
|
82
|
+
* 2. If no _product_.json found → single-product mode (returns empty array)
|
|
83
|
+
* 3. If any found → multi-product mode; bare version folders become the default product
|
|
84
|
+
* 4. Product slugs that match version patterns (e.g., v1.0.0) are rejected with a clear error
|
|
85
|
+
*/
|
|
86
|
+
export declare function scanProducts(): Product[];
|
|
87
|
+
/**
|
|
88
|
+
* Get all products (cached). Returns empty array in single-product mode.
|
|
89
|
+
*/
|
|
90
|
+
export declare function getProducts(): Product[];
|
|
91
|
+
/**
|
|
92
|
+
* Check if the site is in multi-product mode.
|
|
93
|
+
*/
|
|
94
|
+
export declare function isMultiProductMode(): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Clear product-related caches. Called by file watchers when _product_.json changes.
|
|
97
|
+
*/
|
|
98
|
+
export declare function clearProductCaches(): void;
|
|
71
99
|
/**
|
|
72
100
|
* Export the loaded config as default (SERVER ONLY)
|
|
73
101
|
*/
|