specra 0.1.13 → 0.2.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/LICENSE.MD +25 -4
- package/README.md +67 -58
- package/config/specra.config.schema.json +16 -0
- package/config/svelte-config.js +63 -0
- package/dist/api-parser.types.d.ts +59 -0
- package/dist/api-parser.types.js +5 -0
- package/dist/api.types.d.ts +137 -0
- package/dist/api.types.js +5 -0
- package/dist/category.d.ts +21 -0
- package/dist/category.js +48 -0
- package/dist/components/ConfigProvider.svelte +13 -0
- package/dist/components/ConfigProvider.svelte.d.ts +31 -0
- package/dist/components/docs/Accordion.svelte +18 -0
- package/dist/components/docs/Accordion.svelte.d.ts +10 -0
- package/dist/components/docs/AccordionItem.svelte +41 -0
- package/dist/components/docs/AccordionItem.svelte.d.ts +10 -0
- package/dist/components/docs/Badge.svelte +28 -0
- package/dist/components/docs/Badge.svelte.d.ts +9 -0
- package/dist/components/docs/Breadcrumb.svelte +80 -0
- package/dist/components/docs/Breadcrumb.svelte.d.ts +8 -0
- package/dist/components/docs/Callout.svelte +96 -0
- package/dist/components/docs/Callout.svelte.d.ts +10 -0
- package/dist/components/docs/Card.svelte +63 -0
- package/dist/components/docs/Card.svelte.d.ts +12 -0
- package/dist/components/docs/CardGrid.svelte +24 -0
- package/dist/components/docs/CardGrid.svelte.d.ts +8 -0
- package/dist/components/docs/CategoryIndex.svelte +110 -0
- package/dist/components/docs/CategoryIndex.svelte.d.ts +29 -0
- package/dist/components/docs/CodeBlock.svelte +172 -0
- package/dist/components/docs/CodeBlock.svelte.d.ts +8 -0
- package/dist/components/docs/Column.svelte +25 -0
- package/dist/components/docs/Column.svelte.d.ts +8 -0
- package/dist/components/docs/Columns.svelte +38 -0
- package/dist/components/docs/Columns.svelte.d.ts +13 -0
- package/dist/components/docs/DevModeBadge.svelte +15 -0
- package/dist/components/docs/DevModeBadge.svelte.d.ts +18 -0
- package/dist/components/docs/DocBadge.svelte +28 -0
- package/dist/components/docs/DocBadge.svelte.d.ts +9 -0
- package/dist/components/docs/DocLayout.svelte +107 -0
- package/dist/components/docs/DocLayout.svelte.d.ts +32 -0
- package/dist/components/docs/DocLoading.svelte +53 -0
- package/dist/components/docs/DocLoading.svelte.d.ts +18 -0
- package/dist/components/docs/DocMetadata.svelte +106 -0
- package/dist/components/docs/DocMetadata.svelte.d.ts +18 -0
- package/dist/components/docs/DocNavigation.svelte +56 -0
- package/dist/components/docs/DocNavigation.svelte.d.ts +12 -0
- package/dist/components/docs/DocTags.svelte +22 -0
- package/dist/components/docs/DocTags.svelte.d.ts +6 -0
- package/dist/components/docs/DraftBadge.svelte +10 -0
- package/dist/components/docs/DraftBadge.svelte.d.ts +18 -0
- package/dist/components/docs/Footer.svelte +72 -0
- package/dist/components/docs/Footer.svelte.d.ts +7 -0
- package/dist/components/docs/Frame.svelte +27 -0
- package/dist/components/docs/Frame.svelte.d.ts +9 -0
- package/dist/components/docs/Header.svelte +123 -0
- package/dist/components/docs/Header.svelte.d.ts +9 -0
- package/dist/components/docs/HeaderWithMenu.svelte +34 -0
- package/dist/components/docs/HeaderWithMenu.svelte.d.ts +17 -0
- package/dist/components/docs/HotReloadIndicator.svelte +44 -0
- package/dist/components/docs/HotReloadIndicator.svelte.d.ts +3 -0
- package/dist/components/docs/Icon.svelte +103 -0
- package/dist/components/docs/Icon.svelte.d.ts +11 -0
- package/dist/components/docs/Image.svelte +88 -0
- package/dist/components/docs/Image.svelte.d.ts +11 -0
- package/dist/components/docs/ImageCard.svelte +91 -0
- package/dist/components/docs/ImageCard.svelte.d.ts +12 -0
- package/dist/components/docs/ImageCardGrid.svelte +25 -0
- package/dist/components/docs/ImageCardGrid.svelte.d.ts +8 -0
- package/dist/components/docs/LayoutProviders.svelte +57 -0
- package/dist/components/docs/LayoutProviders.svelte.d.ts +9 -0
- package/dist/components/docs/Logo.svelte +25 -0
- package/dist/components/docs/Logo.svelte.d.ts +11 -0
- package/dist/components/docs/Math.svelte +54 -0
- package/dist/components/docs/Math.svelte.d.ts +7 -0
- package/dist/components/docs/MdxContent.svelte +41 -0
- package/dist/components/docs/MdxHotReload.svelte +78 -0
- package/dist/components/docs/MdxHotReload.svelte.d.ts +9 -0
- package/dist/components/docs/MdxLayout.svelte +16 -0
- package/dist/components/docs/MdxLayout.svelte.d.ts +6 -0
- package/dist/components/docs/Mermaid.svelte +88 -0
- package/dist/components/docs/Mermaid.svelte.d.ts +7 -0
- package/dist/components/docs/MobileDocLayout.svelte +211 -0
- package/dist/components/docs/MobileDocLayout.svelte.d.ts +35 -0
- package/dist/components/docs/MobileSidebar.svelte +122 -0
- package/dist/components/docs/MobileSidebar.svelte.d.ts +31 -0
- package/dist/components/docs/MobileSidebarWrapper.svelte +122 -0
- package/dist/components/docs/MobileSidebarWrapper.svelte.d.ts +32 -0
- package/dist/components/docs/NotFoundContent.svelte +40 -0
- package/dist/components/docs/NotFoundContent.svelte.d.ts +6 -0
- package/dist/components/docs/SearchHighlight.svelte +116 -0
- package/dist/components/docs/SearchHighlight.svelte.d.ts +3 -0
- package/dist/components/docs/SearchModal.svelte +239 -0
- package/dist/components/docs/SearchModal.svelte.d.ts +9 -0
- package/dist/components/docs/Sidebar.svelte +69 -0
- package/dist/components/docs/Sidebar.svelte.d.ts +31 -0
- package/dist/components/docs/SidebarMenuItems.svelte +344 -0
- package/dist/components/docs/SidebarMenuItems.svelte.d.ts +33 -0
- package/dist/components/docs/SidebarSkeleton.svelte +50 -0
- package/dist/components/docs/SidebarSkeleton.svelte.d.ts +18 -0
- package/dist/components/docs/SiteBanner.svelte +92 -0
- package/dist/components/docs/SiteBanner.svelte.d.ts +7 -0
- package/dist/components/docs/Step.svelte +44 -0
- package/dist/components/docs/Step.svelte.d.ts +8 -0
- package/dist/components/docs/Steps.svelte +15 -0
- package/dist/components/docs/Steps.svelte.d.ts +7 -0
- package/dist/components/docs/Tab.svelte +40 -0
- package/dist/components/docs/Tab.svelte.d.ts +8 -0
- package/dist/components/docs/TabGroups.svelte +183 -0
- package/dist/components/docs/TabGroups.svelte.d.ts +25 -0
- package/dist/components/docs/TableOfContents.svelte +100 -0
- package/dist/components/docs/TableOfContents.svelte.d.ts +9 -0
- package/dist/components/docs/Tabs.svelte +69 -0
- package/dist/components/docs/Tabs.svelte.d.ts +8 -0
- package/dist/components/docs/ThemeToggle.svelte +16 -0
- package/dist/components/docs/ThemeToggle.svelte.d.ts +18 -0
- package/dist/components/docs/Tooltip.svelte +44 -0
- package/dist/components/docs/Tooltip.svelte.d.ts +10 -0
- package/dist/components/docs/VersionSwitcher.svelte +95 -0
- package/dist/components/docs/VersionSwitcher.svelte.d.ts +7 -0
- package/dist/components/docs/Video.svelte +84 -0
- package/dist/components/docs/Video.svelte.d.ts +12 -0
- package/dist/components/docs/api/ApiEndpoint.svelte +61 -0
- package/dist/components/docs/api/ApiEndpoint.svelte.d.ts +11 -0
- package/dist/components/docs/api/ApiParams.svelte +80 -0
- package/dist/components/docs/api/ApiParams.svelte.d.ts +14 -0
- package/dist/components/docs/api/ApiPlayground.svelte +259 -0
- package/dist/components/docs/api/ApiPlayground.svelte.d.ts +16 -0
- package/dist/components/docs/api/ApiReference.svelte +278 -0
- package/dist/components/docs/api/ApiReference.svelte.d.ts +23 -0
- package/dist/components/docs/api/ApiResponse.svelte +66 -0
- package/dist/components/docs/api/ApiResponse.svelte.d.ts +9 -0
- package/dist/components/docs/api/index.d.ts +5 -0
- package/dist/components/docs/api/index.js +5 -0
- package/dist/components/docs/componentTextProps.d.ts +3 -0
- package/dist/components/docs/componentTextProps.js +61 -0
- package/dist/components/docs/index.d.ts +54 -0
- package/dist/components/docs/index.js +56 -0
- package/dist/components/global/VersionNotFound.svelte +48 -0
- package/dist/components/global/VersionNotFound.svelte.d.ts +7 -0
- package/dist/components/global/index.d.ts +1 -0
- package/dist/components/global/index.js +1 -0
- package/dist/components/index.d.ts +6 -822
- package/dist/components/index.js +11 -3854
- package/dist/components/ui/Badge.svelte +48 -0
- package/dist/components/ui/Badge.svelte.d.ts +15 -0
- package/dist/components/ui/Button.svelte +58 -0
- package/dist/components/ui/Button.svelte.d.ts +17 -0
- package/dist/components/ui/Dialog.svelte +16 -0
- package/dist/components/ui/Dialog.svelte.d.ts +9 -0
- package/dist/components/ui/DialogClose.svelte +16 -0
- package/dist/components/ui/DialogClose.svelte.d.ts +9 -0
- package/dist/components/ui/DialogContent.svelte +43 -0
- package/dist/components/ui/DialogContent.svelte.d.ts +10 -0
- package/dist/components/ui/DialogDescription.svelte +21 -0
- package/dist/components/ui/DialogDescription.svelte.d.ts +9 -0
- package/dist/components/ui/DialogFooter.svelte +20 -0
- package/dist/components/ui/DialogFooter.svelte.d.ts +9 -0
- package/dist/components/ui/DialogHeader.svelte +20 -0
- package/dist/components/ui/DialogHeader.svelte.d.ts +9 -0
- package/dist/components/ui/DialogTitle.svelte +21 -0
- package/dist/components/ui/DialogTitle.svelte.d.ts +9 -0
- package/dist/components/ui/Input.svelte +23 -0
- package/dist/components/ui/Input.svelte.d.ts +8 -0
- package/dist/components/ui/Textarea.svelte +19 -0
- package/dist/components/ui/Textarea.svelte.d.ts +7 -0
- package/dist/components/ui/index.d.ts +11 -0
- package/dist/components/ui/index.js +11 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +9 -0
- package/dist/config.schema.json +471 -0
- package/dist/config.server.d.ts +46 -0
- package/dist/config.server.js +149 -0
- package/dist/{mdx-ColN3Cyg.d.mts → config.types.d.ts} +22 -75
- package/dist/config.types.js +39 -0
- package/dist/dev-utils.d.ts +29 -0
- package/dist/dev-utils.js +63 -0
- package/dist/index.d.ts +19 -4
- package/dist/index.js +25 -4861
- package/dist/mdx-cache.d.ts +41 -0
- package/dist/mdx-cache.js +160 -0
- package/dist/mdx-components.js +50 -1931
- package/dist/mdx-security.d.ts +76 -0
- package/dist/mdx-security.js +217 -0
- package/dist/mdx.d.ts +73 -0
- package/dist/mdx.js +1099 -0
- package/dist/middleware/index.d.ts +1 -0
- package/dist/middleware/index.js +2 -0
- package/dist/middleware/security.d.ts +22 -47
- package/dist/middleware/security.js +111 -137
- package/dist/parsers/base-parser.d.ts +14 -0
- package/dist/parsers/base-parser.js +1 -0
- package/dist/parsers/index.d.ts +16 -0
- package/dist/parsers/index.js +51 -0
- package/dist/parsers/openapi-parser.d.ts +18 -0
- package/dist/parsers/openapi-parser.js +209 -0
- package/dist/parsers/postman-parser.d.ts +20 -0
- package/dist/parsers/postman-parser.js +260 -0
- package/dist/parsers/specra-parser.d.ts +10 -0
- package/dist/parsers/specra-parser.js +18 -0
- package/dist/redirects.d.ts +12 -0
- package/dist/redirects.js +30 -0
- package/dist/remark-code-meta.d.ts +6 -0
- package/dist/remark-code-meta.js +21 -0
- package/dist/sidebar-utils.d.ts +59 -0
- package/dist/sidebar-utils.js +144 -0
- package/dist/stores/config.d.ts +20 -0
- package/dist/stores/config.js +45 -0
- package/dist/stores/index.d.ts +4 -0
- package/dist/stores/index.js +4 -0
- package/dist/stores/sidebar.d.ts +7 -0
- package/dist/stores/sidebar.js +12 -0
- package/dist/stores/tabs.d.ts +6 -0
- package/dist/stores/tabs.js +41 -0
- package/dist/stores/theme.d.ts +7 -0
- package/dist/stores/theme.js +75 -0
- package/dist/{styles.css → styles/globals.css} +136 -6
- package/dist/toc.d.ts +9 -0
- package/dist/toc.js +15 -0
- package/dist/utils.d.ts +13 -0
- package/dist/utils.js +30 -0
- package/package.json +47 -90
- package/dist/app/api/mdx-watch/route.d.mts +0 -10
- package/dist/app/api/mdx-watch/route.d.ts +0 -10
- package/dist/app/api/mdx-watch/route.js +0 -118
- package/dist/app/api/mdx-watch/route.js.map +0 -1
- package/dist/app/api/mdx-watch/route.mjs +0 -91
- package/dist/app/api/mdx-watch/route.mjs.map +0 -1
- package/dist/chunk-6S3EJVEO.mjs +0 -259
- package/dist/chunk-6S3EJVEO.mjs.map +0 -1
- package/dist/chunk-BE7EROIW.mjs +0 -212
- package/dist/chunk-BE7EROIW.mjs.map +0 -1
- package/dist/chunk-CWHRZHZO.mjs +0 -168
- package/dist/chunk-CWHRZHZO.mjs.map +0 -1
- package/dist/chunk-D5VDVYFY.mjs +0 -1325
- package/dist/chunk-D5VDVYFY.mjs.map +0 -1
- package/dist/chunk-WMCO2UX5.mjs +0 -585
- package/dist/chunk-WMCO2UX5.mjs.map +0 -1
- package/dist/chunk-XEMGCPZZ.mjs +0 -475
- package/dist/chunk-XEMGCPZZ.mjs.map +0 -1
- package/dist/components/index.d.mts +0 -822
- package/dist/components/index.js.map +0 -1
- package/dist/components/index.mjs +0 -3741
- package/dist/components/index.mjs.map +0 -1
- package/dist/index.d.mts +0 -4
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -1897
- package/dist/index.mjs.map +0 -1
- package/dist/layouts/index.d.mts +0 -34
- package/dist/layouts/index.d.ts +0 -34
- package/dist/layouts/index.js +0 -453
- package/dist/layouts/index.js.map +0 -1
- package/dist/layouts/index.mjs +0 -173
- package/dist/layouts/index.mjs.map +0 -1
- package/dist/lib/index.d.mts +0 -583
- package/dist/lib/index.d.ts +0 -583
- package/dist/lib/index.js +0 -1595
- package/dist/lib/index.js.map +0 -1
- package/dist/lib/index.mjs +0 -111
- package/dist/lib/index.mjs.map +0 -1
- package/dist/mdx-ColN3Cyg.d.ts +0 -352
- package/dist/mdx-components.d.mts +0 -86
- package/dist/mdx-components.d.ts +0 -86
- package/dist/mdx-components.js.map +0 -1
- package/dist/mdx-components.mjs +0 -206
- package/dist/mdx-components.mjs.map +0 -1
- package/dist/middleware/security.d.mts +0 -82
- package/dist/middleware/security.js.map +0 -1
- package/dist/middleware/security.mjs +0 -84
- package/dist/middleware/security.mjs.map +0 -1
- package/dist/styles.css.map +0 -1
- package/dist/styles.d.mts +0 -2
- package/dist/styles.d.ts +0 -2
- package/dist/styles.js +0 -2
- package/dist/styles.js.map +0 -1
- package/dist/styles.mjs +0 -1
- package/dist/styles.mjs.map +0 -1
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { page } from '$app/stores';
|
|
3
|
+
import { ChevronRight, ChevronDown, FolderOpen, Lock } from 'lucide-svelte';
|
|
4
|
+
import type { SpecraConfig } from '../../config.types.js';
|
|
5
|
+
import Icon from './Icon.svelte';
|
|
6
|
+
import { sortSidebarItems, sortSidebarGroups } from '../../sidebar-utils.js';
|
|
7
|
+
|
|
8
|
+
interface DocItem {
|
|
9
|
+
title: string;
|
|
10
|
+
slug: string;
|
|
11
|
+
filePath: string;
|
|
12
|
+
section?: string;
|
|
13
|
+
group?: string;
|
|
14
|
+
sidebar?: string;
|
|
15
|
+
sidebar_position?: number;
|
|
16
|
+
categoryLabel?: string;
|
|
17
|
+
categoryPosition?: number;
|
|
18
|
+
categoryCollapsible?: boolean;
|
|
19
|
+
categoryCollapsed?: boolean;
|
|
20
|
+
categoryIcon?: string;
|
|
21
|
+
categoryTabGroup?: string;
|
|
22
|
+
meta?: {
|
|
23
|
+
icon?: string;
|
|
24
|
+
tab_group?: string;
|
|
25
|
+
sidebar_position?: number;
|
|
26
|
+
order?: number;
|
|
27
|
+
[key: string]: any;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface SidebarGroup {
|
|
32
|
+
label: string;
|
|
33
|
+
path: string;
|
|
34
|
+
icon?: string;
|
|
35
|
+
items: DocItem[];
|
|
36
|
+
position: number;
|
|
37
|
+
collapsible: boolean;
|
|
38
|
+
defaultCollapsed: boolean;
|
|
39
|
+
children: Record<string, SidebarGroup>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface Props {
|
|
43
|
+
docs: DocItem[];
|
|
44
|
+
version: string;
|
|
45
|
+
onLinkClick?: () => void;
|
|
46
|
+
config: SpecraConfig;
|
|
47
|
+
activeTabGroup?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let { docs, version, onLinkClick, config, activeTabGroup }: Props = $props();
|
|
51
|
+
|
|
52
|
+
let collapsed: Record<string, boolean> = $state({});
|
|
53
|
+
let pathname = $derived($page.url.pathname);
|
|
54
|
+
|
|
55
|
+
// Filter docs by active tab group if tab groups are configured
|
|
56
|
+
let hasTabGroups = $derived(
|
|
57
|
+
config.navigation?.tabGroups && config.navigation.tabGroups.length > 0
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
let filteredDocs = $derived.by(() => {
|
|
61
|
+
if (hasTabGroups && activeTabGroup) {
|
|
62
|
+
return docs.filter((doc) => {
|
|
63
|
+
const docTabGroup = doc.meta?.tab_group || doc.categoryTabGroup;
|
|
64
|
+
if (!docTabGroup) {
|
|
65
|
+
return activeTabGroup === config.navigation?.tabGroups?.[0]?.id;
|
|
66
|
+
}
|
|
67
|
+
return docTabGroup === activeTabGroup;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return docs;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Build hierarchical tree structure
|
|
74
|
+
interface SidebarStructure {
|
|
75
|
+
rootGroups: Record<string, SidebarGroup>;
|
|
76
|
+
standalone: DocItem[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let structure = $derived.by((): SidebarStructure => {
|
|
80
|
+
const rootGroups: Record<string, SidebarGroup> = {};
|
|
81
|
+
const standalone: DocItem[] = [];
|
|
82
|
+
|
|
83
|
+
filteredDocs.forEach((doc) => {
|
|
84
|
+
const pathParts = doc.filePath.split('/');
|
|
85
|
+
const isIndexFile =
|
|
86
|
+
doc.filePath.endsWith('/index') ||
|
|
87
|
+
doc.filePath === 'index' ||
|
|
88
|
+
(pathParts.length > 1 && doc.slug === pathParts.slice(0, -1).join('/'));
|
|
89
|
+
|
|
90
|
+
const customGroup = doc.sidebar || doc.group;
|
|
91
|
+
|
|
92
|
+
if (customGroup) {
|
|
93
|
+
const groupName = customGroup.charAt(0).toUpperCase() + customGroup.slice(1);
|
|
94
|
+
if (!rootGroups[groupName]) {
|
|
95
|
+
rootGroups[groupName] = {
|
|
96
|
+
label: groupName,
|
|
97
|
+
path: customGroup,
|
|
98
|
+
items: [],
|
|
99
|
+
position: 999,
|
|
100
|
+
collapsible: doc.categoryCollapsible ?? true,
|
|
101
|
+
defaultCollapsed: doc.categoryCollapsed ?? false,
|
|
102
|
+
children: {}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (isIndexFile) {
|
|
106
|
+
rootGroups[groupName].position = doc.sidebar_position ?? 999;
|
|
107
|
+
rootGroups[groupName].icon = doc.categoryIcon;
|
|
108
|
+
} else {
|
|
109
|
+
rootGroups[groupName].items.push(doc);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (pathParts.length > 1) {
|
|
115
|
+
const folderParts = pathParts.slice(0, -1);
|
|
116
|
+
let currentLevel = rootGroups;
|
|
117
|
+
let currentPath = '';
|
|
118
|
+
|
|
119
|
+
for (let i = 0; i < folderParts.length; i++) {
|
|
120
|
+
const folder = folderParts[i];
|
|
121
|
+
currentPath = currentPath ? `${currentPath}/${folder}` : folder;
|
|
122
|
+
const folderLabel = folder
|
|
123
|
+
.split('-')
|
|
124
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
125
|
+
.join(' ');
|
|
126
|
+
|
|
127
|
+
if (!currentLevel[folder]) {
|
|
128
|
+
currentLevel[folder] = {
|
|
129
|
+
label:
|
|
130
|
+
doc.categoryLabel && i === folderParts.length - 1
|
|
131
|
+
? doc.categoryLabel
|
|
132
|
+
: folderLabel,
|
|
133
|
+
path: currentPath,
|
|
134
|
+
icon: doc.categoryIcon,
|
|
135
|
+
items: [],
|
|
136
|
+
position: doc.categoryPosition ?? 999,
|
|
137
|
+
collapsible: doc.categoryCollapsible ?? true,
|
|
138
|
+
defaultCollapsed: doc.categoryCollapsed ?? false,
|
|
139
|
+
children: {}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (i === folderParts.length - 1) {
|
|
144
|
+
if (isIndexFile) {
|
|
145
|
+
currentLevel[folder].position =
|
|
146
|
+
doc.categoryPosition ?? doc.sidebar_position ?? 999;
|
|
147
|
+
if (doc.categoryLabel) {
|
|
148
|
+
currentLevel[folder].label = doc.categoryLabel;
|
|
149
|
+
}
|
|
150
|
+
if (doc.categoryIcon) {
|
|
151
|
+
currentLevel[folder].icon = doc.categoryIcon;
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
currentLevel[folder].items.push(doc);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
currentLevel = currentLevel[folder].children;
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
if (!isIndexFile) {
|
|
162
|
+
standalone.push(doc);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return { rootGroups, standalone };
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
let sortedRootGroups = $derived(sortSidebarGroups(structure.rootGroups));
|
|
171
|
+
let sortedStandalone = $derived(sortSidebarItems(structure.standalone));
|
|
172
|
+
|
|
173
|
+
function toggleSection(section: string) {
|
|
174
|
+
collapsed = { ...collapsed, [section]: !collapsed[section] };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isActiveInGroup(group: SidebarGroup): boolean {
|
|
178
|
+
const hasActiveItem = group.items.some(
|
|
179
|
+
(doc) => pathname === `/docs/${version}/${doc.slug}`
|
|
180
|
+
);
|
|
181
|
+
if (hasActiveItem) return true;
|
|
182
|
+
return Object.values(group.children).some((child) => isActiveInGroup(child));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function getGroupHref(group: SidebarGroup): string {
|
|
186
|
+
let groupHref = `/docs/${version}/${group.path}`;
|
|
187
|
+
|
|
188
|
+
if (config.features?.i18n) {
|
|
189
|
+
const i18n = config.features.i18n;
|
|
190
|
+
const locales = typeof i18n === 'object' ? i18n.locales : ['en'];
|
|
191
|
+
const pathParts = pathname?.split('/') || [];
|
|
192
|
+
const potentialLocale = pathParts[3];
|
|
193
|
+
|
|
194
|
+
if (potentialLocale && locales.includes(potentialLocale)) {
|
|
195
|
+
groupHref = `/docs/${version}/${potentialLocale}/${group.path}`;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return groupHref;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function isGroupCollapsed(groupKey: string, group: SidebarGroup): boolean {
|
|
203
|
+
const hasActive = isActiveInGroup(group);
|
|
204
|
+
const isGroupActive = pathname === `/docs/${version}/${group.path}`;
|
|
205
|
+
if (hasActive || isGroupActive) return false;
|
|
206
|
+
return collapsed[groupKey] ?? group.defaultCollapsed;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
type MergedItem =
|
|
210
|
+
| { type: 'group'; key: string; group: SidebarGroup; position: number }
|
|
211
|
+
| { type: 'item'; doc: DocItem; position: number };
|
|
212
|
+
|
|
213
|
+
function getMergedItems(group: SidebarGroup): MergedItem[] {
|
|
214
|
+
const sortedItems = sortSidebarItems(group.items);
|
|
215
|
+
const sortedChildren = sortSidebarGroups(group.children);
|
|
216
|
+
|
|
217
|
+
const merged: MergedItem[] = [
|
|
218
|
+
...sortedChildren.map(([childKey, childGroup]) => ({
|
|
219
|
+
type: 'group' as const,
|
|
220
|
+
key: childKey,
|
|
221
|
+
group: childGroup,
|
|
222
|
+
position: childGroup.position
|
|
223
|
+
})),
|
|
224
|
+
...sortedItems.map((doc) => ({
|
|
225
|
+
type: 'item' as const,
|
|
226
|
+
doc,
|
|
227
|
+
position: doc.sidebar_position ?? doc.meta?.sidebar_position ?? doc.meta?.order ?? 999
|
|
228
|
+
}))
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
merged.sort((a, b) => a.position - b.position);
|
|
232
|
+
return merged;
|
|
233
|
+
}
|
|
234
|
+
</script>
|
|
235
|
+
|
|
236
|
+
<!-- Recursive group renderer component -->
|
|
237
|
+
{#snippet renderGroup(groupKey: string, group: SidebarGroup, depth: number)}
|
|
238
|
+
{@const sortedItems = sortSidebarItems(group.items)}
|
|
239
|
+
{@const sortedChildren = sortSidebarGroups(group.children)}
|
|
240
|
+
{@const hasChildren = sortedChildren.length > 0}
|
|
241
|
+
{@const hasItems = sortedItems.length > 0}
|
|
242
|
+
{@const hasContent = hasChildren || hasItems}
|
|
243
|
+
{@const isGroupActive = pathname === `/docs/${version}/${group.path}`}
|
|
244
|
+
{@const isCollapsed = isGroupCollapsed(groupKey, group)}
|
|
245
|
+
{@const marginLeft = depth > 0 ? 'ml-4' : ''}
|
|
246
|
+
{@const groupHref = getGroupHref(group)}
|
|
247
|
+
{@const mergedItems = getMergedItems(group)}
|
|
248
|
+
|
|
249
|
+
<div class="space-y-1 {marginLeft}">
|
|
250
|
+
<div class="flex items-center group">
|
|
251
|
+
<a
|
|
252
|
+
href={groupHref}
|
|
253
|
+
onclick={(e) => {
|
|
254
|
+
e.preventDefault();
|
|
255
|
+
toggleSection(groupKey);
|
|
256
|
+
}}
|
|
257
|
+
class="flex items-center gap-2 flex-1 px-3 py-2 text-sm font-semibold rounded-l-xl transition-all {isGroupActive
|
|
258
|
+
? 'bg-primary/10 text-primary'
|
|
259
|
+
: 'text-foreground hover:bg-accent/50'}"
|
|
260
|
+
>
|
|
261
|
+
{#if group.icon}
|
|
262
|
+
<Icon icon={group.icon} size={16} className="shrink-0" />
|
|
263
|
+
{:else}
|
|
264
|
+
<FolderOpen size={16} class="shrink-0" />
|
|
265
|
+
{/if}
|
|
266
|
+
{group.label}
|
|
267
|
+
</a>
|
|
268
|
+
|
|
269
|
+
{#if hasContent && group.collapsible && config.navigation?.collapsibleSidebar}
|
|
270
|
+
<button
|
|
271
|
+
onclick={(e) => {
|
|
272
|
+
e.preventDefault();
|
|
273
|
+
e.stopPropagation();
|
|
274
|
+
toggleSection(groupKey);
|
|
275
|
+
}}
|
|
276
|
+
class="p-2 rounded-r-xl transition-all {isGroupActive ? 'hover:bg-primary/20' : 'hover:bg-accent/50'}"
|
|
277
|
+
aria-label={isCollapsed ? 'Expand section' : 'Collapse section'}
|
|
278
|
+
>
|
|
279
|
+
{#if isCollapsed}
|
|
280
|
+
<ChevronRight class="h-4 w-4 {isGroupActive ? 'text-primary' : 'text-muted-foreground'}" />
|
|
281
|
+
{:else}
|
|
282
|
+
<ChevronDown class="h-4 w-4 {isGroupActive ? 'text-primary' : 'text-muted-foreground'}" />
|
|
283
|
+
{/if}
|
|
284
|
+
</button>
|
|
285
|
+
{/if}
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
{#if !isCollapsed && hasContent}
|
|
289
|
+
<div class="ml-4 space-y-1">
|
|
290
|
+
{#each mergedItems as item}
|
|
291
|
+
{#if item.type === 'group'}
|
|
292
|
+
{@render renderGroup(`${groupKey}/${item.key}`, item.group, depth + 1)}
|
|
293
|
+
{:else}
|
|
294
|
+
{@const href = `/docs/${version}/${item.doc.slug}`}
|
|
295
|
+
{@const isActive = pathname === href}
|
|
296
|
+
<a
|
|
297
|
+
{href}
|
|
298
|
+
onclick={onLinkClick}
|
|
299
|
+
class="flex items-center gap-2 px-3 py-2 text-sm rounded-xl transition-all {isActive
|
|
300
|
+
? 'bg-primary/10 text-primary font-medium'
|
|
301
|
+
: 'text-foreground hover:text-foreground hover:bg-accent/50'}"
|
|
302
|
+
>
|
|
303
|
+
{#if item.doc.meta?.icon}
|
|
304
|
+
<Icon icon={item.doc.meta.icon} size={16} className="shrink-0" />
|
|
305
|
+
{/if}
|
|
306
|
+
{item.doc.title}
|
|
307
|
+
{#if item.doc.meta?.isProtected}
|
|
308
|
+
<Lock size={14} class="shrink-0 text-muted-foreground ml-auto" />
|
|
309
|
+
{/if}
|
|
310
|
+
</a>
|
|
311
|
+
{/if}
|
|
312
|
+
{/each}
|
|
313
|
+
</div>
|
|
314
|
+
{/if}
|
|
315
|
+
</div>
|
|
316
|
+
{/snippet}
|
|
317
|
+
|
|
318
|
+
<nav class="space-y-1">
|
|
319
|
+
{#if sortedStandalone.length > 0}
|
|
320
|
+
{#each sortedStandalone as doc (doc.slug)}
|
|
321
|
+
{@const href = `/docs/${version}/${doc.slug}`}
|
|
322
|
+
{@const isActive = pathname === href}
|
|
323
|
+
<a
|
|
324
|
+
{href}
|
|
325
|
+
onclick={onLinkClick}
|
|
326
|
+
class="flex items-center gap-2 px-3 py-2 text-sm rounded-xl transition-all {isActive
|
|
327
|
+
? 'bg-primary/10 text-primary font-medium'
|
|
328
|
+
: 'text-foreground hover:text-foreground hover:bg-accent/50'}"
|
|
329
|
+
>
|
|
330
|
+
{#if doc.meta?.icon}
|
|
331
|
+
<Icon icon={doc.meta.icon} size={16} className="shrink-0" />
|
|
332
|
+
{/if}
|
|
333
|
+
{doc.title}
|
|
334
|
+
{#if doc.meta?.isProtected}
|
|
335
|
+
<Lock size={14} class="shrink-0 text-muted-foreground ml-auto" />
|
|
336
|
+
{/if}
|
|
337
|
+
</a>
|
|
338
|
+
{/each}
|
|
339
|
+
{/if}
|
|
340
|
+
|
|
341
|
+
{#each sortedRootGroups as [groupKey, group] (groupKey)}
|
|
342
|
+
{@render renderGroup(groupKey, group, 0)}
|
|
343
|
+
{/each}
|
|
344
|
+
</nav>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { SpecraConfig } from '../../config.types.js';
|
|
2
|
+
interface DocItem {
|
|
3
|
+
title: string;
|
|
4
|
+
slug: string;
|
|
5
|
+
filePath: string;
|
|
6
|
+
section?: string;
|
|
7
|
+
group?: string;
|
|
8
|
+
sidebar?: string;
|
|
9
|
+
sidebar_position?: number;
|
|
10
|
+
categoryLabel?: string;
|
|
11
|
+
categoryPosition?: number;
|
|
12
|
+
categoryCollapsible?: boolean;
|
|
13
|
+
categoryCollapsed?: boolean;
|
|
14
|
+
categoryIcon?: string;
|
|
15
|
+
categoryTabGroup?: string;
|
|
16
|
+
meta?: {
|
|
17
|
+
icon?: string;
|
|
18
|
+
tab_group?: string;
|
|
19
|
+
sidebar_position?: number;
|
|
20
|
+
order?: number;
|
|
21
|
+
[key: string]: any;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
interface Props {
|
|
25
|
+
docs: DocItem[];
|
|
26
|
+
version: string;
|
|
27
|
+
onLinkClick?: () => void;
|
|
28
|
+
config: SpecraConfig;
|
|
29
|
+
activeTabGroup?: string;
|
|
30
|
+
}
|
|
31
|
+
declare const SidebarMenuItems: import("svelte").Component<Props, {}, "">;
|
|
32
|
+
type SidebarMenuItems = ReturnType<typeof SidebarMenuItems>;
|
|
33
|
+
export default SidebarMenuItems;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// SidebarSkeleton: Loading skeleton for the sidebar navigation
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<div class="animate-pulse space-y-4 px-4 py-6">
|
|
6
|
+
<!-- Search skeleton -->
|
|
7
|
+
<div class="h-9 bg-muted rounded-md w-full"></div>
|
|
8
|
+
|
|
9
|
+
<!-- Group 1 -->
|
|
10
|
+
<div class="space-y-2">
|
|
11
|
+
<div class="h-4 bg-muted rounded-md w-24"></div>
|
|
12
|
+
<div class="space-y-1.5 pl-2">
|
|
13
|
+
<div class="h-4 bg-muted rounded-md w-36"></div>
|
|
14
|
+
<div class="h-4 bg-muted rounded-md w-28"></div>
|
|
15
|
+
<div class="h-4 bg-muted rounded-md w-32"></div>
|
|
16
|
+
<div class="h-4 bg-muted rounded-md w-24"></div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<!-- Group 2 -->
|
|
21
|
+
<div class="space-y-2">
|
|
22
|
+
<div class="h-4 bg-muted rounded-md w-20"></div>
|
|
23
|
+
<div class="space-y-1.5 pl-2">
|
|
24
|
+
<div class="h-4 bg-muted rounded-md w-32"></div>
|
|
25
|
+
<div class="h-4 bg-muted rounded-md w-36"></div>
|
|
26
|
+
<div class="h-4 bg-muted rounded-md w-24"></div>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<!-- Group 3 -->
|
|
31
|
+
<div class="space-y-2">
|
|
32
|
+
<div class="h-4 bg-muted rounded-md w-28"></div>
|
|
33
|
+
<div class="space-y-1.5 pl-2">
|
|
34
|
+
<div class="h-4 bg-muted rounded-md w-28"></div>
|
|
35
|
+
<div class="h-4 bg-muted rounded-md w-36"></div>
|
|
36
|
+
<div class="h-4 bg-muted rounded-md w-32"></div>
|
|
37
|
+
<div class="h-4 bg-muted rounded-md w-20"></div>
|
|
38
|
+
<div class="h-4 bg-muted rounded-md w-28"></div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<!-- Group 4 -->
|
|
43
|
+
<div class="space-y-2">
|
|
44
|
+
<div class="h-4 bg-muted rounded-md w-24"></div>
|
|
45
|
+
<div class="space-y-1.5 pl-2">
|
|
46
|
+
<div class="h-4 bg-muted rounded-md w-32"></div>
|
|
47
|
+
<div class="h-4 bg-muted rounded-md w-28"></div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const SidebarSkeleton: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type SidebarSkeleton = InstanceType<typeof SidebarSkeleton>;
|
|
18
|
+
export default SidebarSkeleton;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Info, AlertTriangle, CheckCircle, XCircle, X } from 'lucide-svelte';
|
|
3
|
+
import { browser } from '$app/environment';
|
|
4
|
+
import type { SpecraConfig } from '../../config.types.js';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
config: SpecraConfig;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let { config }: Props = $props();
|
|
11
|
+
|
|
12
|
+
const banner = $derived(config.banner);
|
|
13
|
+
const isEnabled = $derived(banner?.enabled && banner?.message);
|
|
14
|
+
const bannerType = $derived(banner?.type || 'info');
|
|
15
|
+
const isDismissible = $derived(banner?.dismissible !== false);
|
|
16
|
+
|
|
17
|
+
let isDismissed = $state(false);
|
|
18
|
+
|
|
19
|
+
// Safe hash for storage key (handles non-Latin1 chars like emojis)
|
|
20
|
+
function safeHash(str: string): string {
|
|
21
|
+
let hash = 0;
|
|
22
|
+
for (let i = 0; i < str.length; i++) {
|
|
23
|
+
const char = str.charCodeAt(i);
|
|
24
|
+
hash = ((hash << 5) - hash) + char;
|
|
25
|
+
hash |= 0;
|
|
26
|
+
}
|
|
27
|
+
return Math.abs(hash).toString(36);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Check localStorage for dismissal state
|
|
31
|
+
$effect(() => {
|
|
32
|
+
if (!browser || !banner?.message) return;
|
|
33
|
+
const storageKey = `specra-banner-dismissed-${safeHash(banner.message)}`;
|
|
34
|
+
const dismissed = localStorage.getItem(storageKey);
|
|
35
|
+
if (dismissed === 'true') {
|
|
36
|
+
isDismissed = true;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
function dismiss() {
|
|
41
|
+
isDismissed = true;
|
|
42
|
+
if (browser && banner?.message) {
|
|
43
|
+
const storageKey = `specra-banner-dismissed-${safeHash(banner.message)}`;
|
|
44
|
+
localStorage.setItem(storageKey, 'true');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const typeStyles: Record<string, string> = {
|
|
49
|
+
info: 'bg-blue-500/10 border-blue-500/20 text-blue-700 dark:text-blue-300',
|
|
50
|
+
warning: 'bg-yellow-500/10 border-yellow-500/20 text-yellow-700 dark:text-yellow-300',
|
|
51
|
+
success: 'bg-green-500/10 border-green-500/20 text-green-700 dark:text-green-300',
|
|
52
|
+
error: 'bg-red-500/10 border-red-500/20 text-red-700 dark:text-red-300'
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const iconColor: Record<string, string> = {
|
|
56
|
+
info: 'text-blue-500',
|
|
57
|
+
warning: 'text-yellow-500',
|
|
58
|
+
success: 'text-green-500',
|
|
59
|
+
error: 'text-red-500'
|
|
60
|
+
};
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
{#if isEnabled && !isDismissed}
|
|
64
|
+
<div
|
|
65
|
+
class="relative flex items-center justify-center gap-2 px-4 py-2.5 text-sm border-b {typeStyles[bannerType] || typeStyles.info}"
|
|
66
|
+
role="banner"
|
|
67
|
+
>
|
|
68
|
+
<span class="shrink-0 {iconColor[bannerType] || iconColor.info}">
|
|
69
|
+
{#if bannerType === 'info'}
|
|
70
|
+
<Info class="h-4 w-4" />
|
|
71
|
+
{:else if bannerType === 'warning'}
|
|
72
|
+
<AlertTriangle class="h-4 w-4" />
|
|
73
|
+
{:else if bannerType === 'success'}
|
|
74
|
+
<CheckCircle class="h-4 w-4" />
|
|
75
|
+
{:else if bannerType === 'error'}
|
|
76
|
+
<XCircle class="h-4 w-4" />
|
|
77
|
+
{/if}
|
|
78
|
+
</span>
|
|
79
|
+
|
|
80
|
+
<span class="text-center font-medium">{banner?.message}</span>
|
|
81
|
+
|
|
82
|
+
{#if isDismissible}
|
|
83
|
+
<button
|
|
84
|
+
onclick={dismiss}
|
|
85
|
+
class="absolute right-3 top-1/2 -translate-y-1/2 p-1 rounded-md hover:bg-black/10 dark:hover:bg-white/10 transition-colors"
|
|
86
|
+
aria-label="Dismiss banner"
|
|
87
|
+
>
|
|
88
|
+
<X class="h-3.5 w-3.5" />
|
|
89
|
+
</button>
|
|
90
|
+
{/if}
|
|
91
|
+
</div>
|
|
92
|
+
{/if}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
title: string;
|
|
6
|
+
children?: Snippet;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { title, children }: Props = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<div class="step-item relative pl-8 pb-6 border-l-2 border-border last:border-l-0 last:pb-0">
|
|
13
|
+
<div class="mb-2">
|
|
14
|
+
<h3 class="text-lg font-semibold text-foreground">{title}</h3>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="prose prose-sm dark:prose-invert max-w-none [&>*:last-child]:mb-0">
|
|
17
|
+
{#if children}
|
|
18
|
+
{@render children()}
|
|
19
|
+
{/if}
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<style>
|
|
24
|
+
.step-item {
|
|
25
|
+
counter-increment: step;
|
|
26
|
+
}
|
|
27
|
+
.step-item::before {
|
|
28
|
+
content: counter(step);
|
|
29
|
+
position: absolute;
|
|
30
|
+
left: 0;
|
|
31
|
+
transform: translateX(-50%);
|
|
32
|
+
width: 2rem;
|
|
33
|
+
height: 2rem;
|
|
34
|
+
border-radius: 9999px;
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
font-size: 0.875rem;
|
|
39
|
+
font-weight: 600;
|
|
40
|
+
z-index: 10;
|
|
41
|
+
background: hsl(var(--primary));
|
|
42
|
+
color: hsl(var(--primary-foreground));
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
children?: Snippet;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let { children }: Props = $props();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<div class="my-6 ml-4 space-y-6" style="counter-reset: step;">
|
|
12
|
+
{#if children}
|
|
13
|
+
{@render children()}
|
|
14
|
+
{/if}
|
|
15
|
+
</div>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getContext, onMount } from 'svelte';
|
|
3
|
+
import type { Writable } from 'svelte/store';
|
|
4
|
+
import type { Snippet } from 'svelte';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
label: string;
|
|
8
|
+
children?: Snippet;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { label, children }: Props = $props();
|
|
12
|
+
|
|
13
|
+
const ctx = getContext<{
|
|
14
|
+
registerTab: (label: string) => void;
|
|
15
|
+
activeTab: Writable<string>;
|
|
16
|
+
setActiveTab: (label: string) => void;
|
|
17
|
+
}>('specra-tabs');
|
|
18
|
+
|
|
19
|
+
let isActive = $state(false);
|
|
20
|
+
|
|
21
|
+
onMount(() => {
|
|
22
|
+
ctx.registerTab(label);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Subscribe to active tab changes
|
|
26
|
+
$effect(() => {
|
|
27
|
+
const unsub = ctx.activeTab.subscribe((value) => {
|
|
28
|
+
isActive = value === label;
|
|
29
|
+
});
|
|
30
|
+
return unsub;
|
|
31
|
+
});
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
{#if isActive}
|
|
35
|
+
<div class="prose prose-slate dark:prose-invert max-w-none [&>*:first-child]:mt-0">
|
|
36
|
+
{#if children}
|
|
37
|
+
{@render children()}
|
|
38
|
+
{/if}
|
|
39
|
+
</div>
|
|
40
|
+
{/if}
|