@clidey/dory 0.30.0 → 0.31.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/bin/dist/dory.js +1 -8
- package/bin/dist/dory.js.map +1 -1
- package/index.html +54 -0
- package/package.json +12 -4
- package/src/app.css +5 -0
- package/src/app.tsx +13 -0
- package/src/components/dropdown.tsx +53 -0
- package/src/components/hooks.tsx +36 -0
- package/src/components/loading.tsx +21 -0
- package/src/components/mdx-provider.tsx +13 -0
- package/src/components/navigation.tsx +58 -0
- package/src/components/notification.tsx +13 -0
- package/src/components/routes.tsx +92 -0
- package/src/components/search-index.ts +43 -0
- package/src/components/search.tsx +448 -0
- package/src/components/store.ts +216 -0
- package/src/config/mdx.js +12 -0
- package/src/config/mdx.ts +12 -0
- package/src/embed/loader.ts +200 -0
- package/src/embed/types.ts +54 -0
- package/src/embed/widget-iframe.ts +302 -0
- package/src/main.tsx +22 -0
- package/src/mdx/accordion.tsx +41 -0
- package/src/mdx/api-playground.tsx +659 -0
- package/src/mdx/api.tsx +354 -0
- package/src/mdx/async-api.tsx +353 -0
- package/src/mdx/callout.tsx +64 -0
- package/src/mdx/card.tsx +54 -0
- package/src/mdx/code.tsx +289 -0
- package/src/mdx/expandable.tsx +36 -0
- package/src/mdx/fence.tsx +74 -0
- package/src/mdx/icon.tsx +73 -0
- package/src/mdx/latex.tsx +26 -0
- package/src/mdx/list.tsx +7 -0
- package/src/mdx/mdx.tsx +47 -0
- package/src/mdx/mermaid.tsx +147 -0
- package/src/mdx/open-api.tsx +311 -0
- package/src/mdx/processor.ts +174 -0
- package/src/mdx/source.tsx +58 -0
- package/src/mdx/step.tsx +51 -0
- package/src/mdx/table.tsx +20 -0
- package/src/mdx/tag.tsx +53 -0
- package/src/mdx/text.tsx +99 -0
- package/src/mdx/websocket-playground.tsx +824 -0
- package/src/plugins/docs-assets.ts +79 -0
- package/src/plugins/frontmatter-dev-server.ts +158 -0
- package/src/plugins/frontmatter-generator.ts +157 -0
- package/src/plugins/html-files-middleware.ts +108 -0
- package/src/plugins/html-metadata-injector.ts +76 -0
- package/src/plugins/llm-txt-dev-server.ts +183 -0
- package/src/plugins/llm-txt-generator.ts +186 -0
- package/src/plugins/sanitize.ts +163 -0
- package/src/plugins/unified-mdx-plugin.ts +35 -0
- package/src/sdk/index.ts +89 -0
- package/src/sdk/react.tsx +139 -0
- package/src/sdk/vue.ts +107 -0
- package/src/theme.scss +72 -0
- package/src/types/config.ts +89 -0
- package/src/ui/header.tsx +96 -0
- package/src/ui/layout.tsx +237 -0
- package/src/ui/mobile-navigation.tsx +72 -0
- package/src/ui/prev-next-link.tsx +74 -0
- package/src/ui/table-of-content.tsx +167 -0
- package/src/utils/colors.ts +54 -0
- package/src/utils/fonts.ts +23 -0
- package/src/utils/functions.ts +12 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.app.json +33 -0
- package/tsconfig.bin.json +25 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +26 -0
- package/vite.config.embed-loader.ts +28 -0
- package/vite.config.embed-widget.ts +31 -0
- package/vite.config.ts +45 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dory Embed Loader Script (Stage 1)
|
|
3
|
+
* This is the minimal initial script that sets up the global API
|
|
4
|
+
* and lazy-loads the widget on demand.
|
|
5
|
+
* Target size: ~3-5KB gzipped
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { DoryEmbedConfig, DoryDocsAPI } from './types';
|
|
9
|
+
|
|
10
|
+
// Prevent double-loading
|
|
11
|
+
if (typeof window !== 'undefined' && window.DoryDocs) {
|
|
12
|
+
console.warn('[DoryDocs] Already loaded');
|
|
13
|
+
} else {
|
|
14
|
+
// Main initialization
|
|
15
|
+
(function() {
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
// Extract base URL from script tag
|
|
19
|
+
const currentScript = document.currentScript as HTMLScriptElement;
|
|
20
|
+
const scriptSrc = currentScript?.src || '';
|
|
21
|
+
const baseUrl = scriptSrc.replace(/\/embed\.js.*$/, '');
|
|
22
|
+
|
|
23
|
+
// Configuration storage
|
|
24
|
+
let config: DoryEmbedConfig = {
|
|
25
|
+
baseUrl,
|
|
26
|
+
position: 'right',
|
|
27
|
+
width: 500,
|
|
28
|
+
height: '100vh',
|
|
29
|
+
theme: 'inherit',
|
|
30
|
+
initialPath: '/',
|
|
31
|
+
autoInit: true,
|
|
32
|
+
showOverlay: true,
|
|
33
|
+
closeOnEscape: true,
|
|
34
|
+
closeOnOverlayClick: true,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Widget instance (loaded lazily)
|
|
38
|
+
let widgetInstance: any = null;
|
|
39
|
+
let widgetLoading = false;
|
|
40
|
+
let widgetLoadPromise: Promise<any> | null = null;
|
|
41
|
+
|
|
42
|
+
// Load the widget script
|
|
43
|
+
async function loadWidget(): Promise<any> {
|
|
44
|
+
if (widgetInstance) return widgetInstance;
|
|
45
|
+
if (widgetLoading) return widgetLoadPromise;
|
|
46
|
+
|
|
47
|
+
widgetLoading = true;
|
|
48
|
+
widgetLoadPromise = new Promise((resolve, reject) => {
|
|
49
|
+
const script = document.createElement('script');
|
|
50
|
+
script.src = `${baseUrl}/embed-widget.js`;
|
|
51
|
+
script.async = true;
|
|
52
|
+
script.type = 'module';
|
|
53
|
+
|
|
54
|
+
script.onload = () => {
|
|
55
|
+
// Widget script exposes window.__DoryWidget__
|
|
56
|
+
if (window.__DoryWidget__) {
|
|
57
|
+
widgetInstance = new window.__DoryWidget__(config);
|
|
58
|
+
resolve(widgetInstance);
|
|
59
|
+
} else {
|
|
60
|
+
reject(new Error('[DoryDocs] Widget failed to load'));
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
script.onerror = () => {
|
|
65
|
+
widgetLoading = false;
|
|
66
|
+
widgetLoadPromise = null;
|
|
67
|
+
reject(new Error('[DoryDocs] Failed to load widget script'));
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
document.head.appendChild(script);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return widgetLoadPromise;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Public API
|
|
77
|
+
const DoryDocs: DoryDocsAPI = {
|
|
78
|
+
// Configuration
|
|
79
|
+
init(userConfig: Partial<DoryEmbedConfig>) {
|
|
80
|
+
config = { ...config, ...userConfig };
|
|
81
|
+
|
|
82
|
+
// Auto-attach trigger if specified
|
|
83
|
+
if (config.trigger) {
|
|
84
|
+
const attachTrigger = () => {
|
|
85
|
+
const element = typeof config.trigger === 'string'
|
|
86
|
+
? document.querySelector(config.trigger)
|
|
87
|
+
: config.trigger;
|
|
88
|
+
|
|
89
|
+
if (element) {
|
|
90
|
+
element.addEventListener('click', () => this.open());
|
|
91
|
+
} else if (typeof config.trigger === 'string') {
|
|
92
|
+
console.warn(`[DoryDocs] Trigger element not found: ${config.trigger}`);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Try immediately, or wait for DOM ready
|
|
97
|
+
if (document.readyState === 'loading') {
|
|
98
|
+
document.addEventListener('DOMContentLoaded', attachTrigger);
|
|
99
|
+
} else {
|
|
100
|
+
attachTrigger();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return this;
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
// Open documentation
|
|
108
|
+
async open(path?: string) {
|
|
109
|
+
try {
|
|
110
|
+
const widget = await loadWidget();
|
|
111
|
+
await widget.open(path || config.initialPath);
|
|
112
|
+
config.onOpen?.();
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error('[DoryDocs] Failed to open:', error);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
// Close documentation
|
|
119
|
+
async close() {
|
|
120
|
+
if (!widgetInstance) return;
|
|
121
|
+
widgetInstance.close();
|
|
122
|
+
config.onClose?.();
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// Toggle documentation
|
|
126
|
+
async toggle() {
|
|
127
|
+
try {
|
|
128
|
+
const widget = await loadWidget();
|
|
129
|
+
widget.toggle();
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error('[DoryDocs] Failed to toggle:', error);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
// Navigate to path
|
|
136
|
+
async navigate(path: string) {
|
|
137
|
+
try {
|
|
138
|
+
const widget = await loadWidget();
|
|
139
|
+
widget.navigate(path);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error('[DoryDocs] Failed to navigate:', error);
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
// Set theme
|
|
146
|
+
async setTheme(theme: 'light' | 'dark') {
|
|
147
|
+
try {
|
|
148
|
+
const widget = await loadWidget();
|
|
149
|
+
widget.setTheme(theme);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('[DoryDocs] Failed to set theme:', error);
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// Get current state
|
|
156
|
+
isOpen() {
|
|
157
|
+
return widgetInstance?.isOpen() || false;
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
// Destroy widget
|
|
161
|
+
async destroy() {
|
|
162
|
+
if (widgetInstance) {
|
|
163
|
+
widgetInstance.destroy();
|
|
164
|
+
widgetInstance = null;
|
|
165
|
+
widgetLoading = false;
|
|
166
|
+
widgetLoadPromise = null;
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
// Event listeners
|
|
171
|
+
on(event: string, handler: Function) {
|
|
172
|
+
const eventKey = `on${event.charAt(0).toUpperCase()}${event.slice(1)}` as keyof DoryEmbedConfig;
|
|
173
|
+
(config as any)[eventKey] = handler;
|
|
174
|
+
if (widgetInstance) {
|
|
175
|
+
widgetInstance.on(event, handler);
|
|
176
|
+
}
|
|
177
|
+
return this;
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Expose global API
|
|
182
|
+
window.DoryDocs = DoryDocs;
|
|
183
|
+
|
|
184
|
+
// Auto-initialize if configured
|
|
185
|
+
if (config.autoInit) {
|
|
186
|
+
// Check for URL hash deep linking
|
|
187
|
+
if (window.location.hash.startsWith('#docs=')) {
|
|
188
|
+
const path = decodeURIComponent(window.location.hash.replace('#docs=', ''));
|
|
189
|
+
DoryDocs.open(path);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Dispatch ready event
|
|
194
|
+
window.dispatchEvent(new CustomEvent('dory:ready', { detail: { api: DoryDocs } }));
|
|
195
|
+
|
|
196
|
+
// Call onReady callback
|
|
197
|
+
config.onReady?.();
|
|
198
|
+
|
|
199
|
+
})();
|
|
200
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript interfaces for Dory embedded documentation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface DoryEmbedConfig {
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
position?: 'left' | 'right' | 'bottom';
|
|
8
|
+
width?: number | string;
|
|
9
|
+
height?: string;
|
|
10
|
+
theme?: 'light' | 'dark' | 'inherit';
|
|
11
|
+
initialPath?: string;
|
|
12
|
+
trigger?: string | HTMLElement;
|
|
13
|
+
autoInit?: boolean;
|
|
14
|
+
showOverlay?: boolean;
|
|
15
|
+
closeOnEscape?: boolean;
|
|
16
|
+
closeOnOverlayClick?: boolean;
|
|
17
|
+
onOpen?: () => void;
|
|
18
|
+
onClose?: () => void;
|
|
19
|
+
onNavigate?: (path: string) => void;
|
|
20
|
+
onReady?: () => void;
|
|
21
|
+
authToken?: string;
|
|
22
|
+
getAuthToken?: () => Promise<string>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface EmbeddedAppConfig {
|
|
26
|
+
container: HTMLElement;
|
|
27
|
+
config: DoryEmbedConfig;
|
|
28
|
+
onNavigate: (path: string) => void;
|
|
29
|
+
onClose: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface RouterContext {
|
|
33
|
+
currentPath: string;
|
|
34
|
+
navigate: (path: string) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
declare global {
|
|
38
|
+
interface Window {
|
|
39
|
+
DoryDocs: DoryDocsAPI;
|
|
40
|
+
__DoryWidget__: any;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface DoryDocsAPI {
|
|
45
|
+
init(userConfig: Partial<DoryEmbedConfig>): DoryDocsAPI;
|
|
46
|
+
open(path?: string): Promise<void>;
|
|
47
|
+
close(): Promise<void>;
|
|
48
|
+
toggle(): Promise<void>;
|
|
49
|
+
navigate(path: string): Promise<void>;
|
|
50
|
+
setTheme(theme: 'light' | 'dark'): Promise<void>;
|
|
51
|
+
isOpen(): boolean;
|
|
52
|
+
destroy(): Promise<void>;
|
|
53
|
+
on(event: string, handler: Function): DoryDocsAPI;
|
|
54
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dory Widget Shell (Stage 2) - Iframe Approach
|
|
3
|
+
* Creates an iframe pointing to the already-built documentation
|
|
4
|
+
* This is simpler and more reliable than rebuilding the app
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { DoryEmbedConfig } from './types';
|
|
8
|
+
|
|
9
|
+
export class DoryWidget {
|
|
10
|
+
private config: DoryEmbedConfig;
|
|
11
|
+
private container: HTMLElement | null = null;
|
|
12
|
+
private iframe: HTMLIFrameElement | null = null;
|
|
13
|
+
private overlay: HTMLElement | null = null;
|
|
14
|
+
private isOpenState = false;
|
|
15
|
+
private eventHandlers: Map<string, Function[]> = new Map();
|
|
16
|
+
|
|
17
|
+
constructor(config: DoryEmbedConfig) {
|
|
18
|
+
this.config = config;
|
|
19
|
+
this.init();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private init() {
|
|
23
|
+
this.createContainer();
|
|
24
|
+
this.setupEventListeners();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private createContainer() {
|
|
28
|
+
// Create overlay
|
|
29
|
+
if (this.config.showOverlay !== false) {
|
|
30
|
+
this.overlay = document.createElement('div');
|
|
31
|
+
this.overlay.className = 'dory-embed-overlay';
|
|
32
|
+
this.overlay.style.cssText = `
|
|
33
|
+
position: fixed;
|
|
34
|
+
inset: 0;
|
|
35
|
+
background: rgba(0, 0, 0, 0.5);
|
|
36
|
+
z-index: 999998;
|
|
37
|
+
opacity: 0;
|
|
38
|
+
pointer-events: none;
|
|
39
|
+
transition: opacity 0.3s ease;
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
if (this.config.closeOnOverlayClick !== false) {
|
|
43
|
+
this.overlay.addEventListener('click', () => this.close());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
document.body.appendChild(this.overlay);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Create main container
|
|
50
|
+
this.container = document.createElement('div');
|
|
51
|
+
this.container.className = 'dory-embed-container';
|
|
52
|
+
this.container.setAttribute('data-position', this.config.position || 'right');
|
|
53
|
+
this.container.style.cssText = this.getContainerStyles();
|
|
54
|
+
|
|
55
|
+
// Create iframe
|
|
56
|
+
this.iframe = document.createElement('iframe');
|
|
57
|
+
this.iframe.style.cssText = 'width: 100%; height: 100%; border: none;';
|
|
58
|
+
this.iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups');
|
|
59
|
+
this.iframe.src = this.buildIframeUrl();
|
|
60
|
+
|
|
61
|
+
this.container.appendChild(this.iframe);
|
|
62
|
+
|
|
63
|
+
// Add container to body
|
|
64
|
+
document.body.appendChild(this.container);
|
|
65
|
+
|
|
66
|
+
// Setup iframe communication
|
|
67
|
+
this.setupIframeMessaging();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private buildIframeUrl(): string {
|
|
71
|
+
const baseUrl = this.config.baseUrl;
|
|
72
|
+
const path = this.config.initialPath || '/';
|
|
73
|
+
const params = new URLSearchParams();
|
|
74
|
+
|
|
75
|
+
// Add embedded mode flag
|
|
76
|
+
params.set('embedded', 'true');
|
|
77
|
+
|
|
78
|
+
// Add theme
|
|
79
|
+
if (this.config.theme && this.config.theme !== 'inherit') {
|
|
80
|
+
params.set('theme', this.config.theme);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const url = new URL(baseUrl);
|
|
84
|
+
url.pathname = path;
|
|
85
|
+
url.search = params.toString();
|
|
86
|
+
|
|
87
|
+
return url.toString();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private setupIframeMessaging() {
|
|
91
|
+
window.addEventListener('message', (event) => {
|
|
92
|
+
// Verify origin
|
|
93
|
+
if (event.origin !== new URL(this.config.baseUrl).origin) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const message = event.data;
|
|
98
|
+
|
|
99
|
+
switch (message.type) {
|
|
100
|
+
case 'navigate':
|
|
101
|
+
this.emit('navigate', message.path);
|
|
102
|
+
break;
|
|
103
|
+
case 'ready':
|
|
104
|
+
this.emit('ready');
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private getContainerStyles(): string {
|
|
111
|
+
const { position = 'right', width = 500, height = '100vh' } = this.config;
|
|
112
|
+
const widthStr = typeof width === 'number' ? `${width}px` : width;
|
|
113
|
+
|
|
114
|
+
const baseStyles = `
|
|
115
|
+
position: fixed;
|
|
116
|
+
z-index: 999999;
|
|
117
|
+
background: white;
|
|
118
|
+
box-shadow: -2px 0 20px rgba(0, 0, 0, 0.15);
|
|
119
|
+
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
120
|
+
will-change: transform;
|
|
121
|
+
overflow: hidden;
|
|
122
|
+
`;
|
|
123
|
+
|
|
124
|
+
const positionStyles = {
|
|
125
|
+
right: `
|
|
126
|
+
top: 0;
|
|
127
|
+
right: 0;
|
|
128
|
+
bottom: 0;
|
|
129
|
+
width: ${widthStr};
|
|
130
|
+
max-width: 100vw;
|
|
131
|
+
transform: translateX(100%);
|
|
132
|
+
`,
|
|
133
|
+
left: `
|
|
134
|
+
top: 0;
|
|
135
|
+
left: 0;
|
|
136
|
+
bottom: 0;
|
|
137
|
+
width: ${widthStr};
|
|
138
|
+
max-width: 100vw;
|
|
139
|
+
transform: translateX(-100%);
|
|
140
|
+
`,
|
|
141
|
+
bottom: `
|
|
142
|
+
bottom: 0;
|
|
143
|
+
left: 0;
|
|
144
|
+
right: 0;
|
|
145
|
+
height: ${height};
|
|
146
|
+
max-height: 90vh;
|
|
147
|
+
transform: translateY(100%);
|
|
148
|
+
`,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return baseStyles + positionStyles[position];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private setupEventListeners() {
|
|
155
|
+
// Keyboard shortcuts
|
|
156
|
+
const handleKeydown = (e: KeyboardEvent) => {
|
|
157
|
+
// ESC to close
|
|
158
|
+
if (e.key === 'Escape' && this.isOpenState && this.config.closeOnEscape !== false) {
|
|
159
|
+
this.close();
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
document.addEventListener('keydown', handleKeydown);
|
|
164
|
+
|
|
165
|
+
// Store cleanup function
|
|
166
|
+
(this.container as any).__cleanup = () => {
|
|
167
|
+
document.removeEventListener('keydown', handleKeydown);
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async open(path?: string) {
|
|
172
|
+
if (this.isOpenState) {
|
|
173
|
+
if (path) this.navigate(path);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
this.isOpenState = true;
|
|
178
|
+
|
|
179
|
+
// Show overlay
|
|
180
|
+
if (this.overlay) {
|
|
181
|
+
this.overlay.style.opacity = '1';
|
|
182
|
+
this.overlay.style.pointerEvents = 'auto';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Slide in container
|
|
186
|
+
if (this.container) {
|
|
187
|
+
this.container.style.transform = 'translate(0, 0)';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Update iframe URL if path is specified
|
|
191
|
+
if (path && this.iframe) {
|
|
192
|
+
const url = new URL(this.iframe.src);
|
|
193
|
+
url.pathname = path;
|
|
194
|
+
this.iframe.src = url.toString();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Focus management
|
|
198
|
+
this.container?.focus();
|
|
199
|
+
|
|
200
|
+
// Disable body scroll
|
|
201
|
+
document.body.style.overflow = 'hidden';
|
|
202
|
+
|
|
203
|
+
// Emit event
|
|
204
|
+
this.emit('open');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
close() {
|
|
208
|
+
if (!this.isOpenState) return;
|
|
209
|
+
|
|
210
|
+
this.isOpenState = false;
|
|
211
|
+
|
|
212
|
+
// Hide overlay
|
|
213
|
+
if (this.overlay) {
|
|
214
|
+
this.overlay.style.opacity = '0';
|
|
215
|
+
this.overlay.style.pointerEvents = 'none';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Slide out container
|
|
219
|
+
if (this.container) {
|
|
220
|
+
const { position = 'right' } = this.config;
|
|
221
|
+
const transforms: Record<string, string> = {
|
|
222
|
+
right: 'translateX(100%)',
|
|
223
|
+
left: 'translateX(-100%)',
|
|
224
|
+
bottom: 'translateY(100%)',
|
|
225
|
+
};
|
|
226
|
+
this.container.style.transform = transforms[position];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Re-enable body scroll
|
|
230
|
+
document.body.style.overflow = '';
|
|
231
|
+
|
|
232
|
+
// Emit event
|
|
233
|
+
this.emit('close');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
toggle() {
|
|
237
|
+
this.isOpenState ? this.close() : this.open();
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
navigate(path: string) {
|
|
241
|
+
if (this.iframe) {
|
|
242
|
+
const url = new URL(this.iframe.src);
|
|
243
|
+
url.pathname = path;
|
|
244
|
+
this.iframe.src = url.toString();
|
|
245
|
+
this.emit('navigate', path);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
setTheme(theme: 'light' | 'dark') {
|
|
250
|
+
if (this.iframe) {
|
|
251
|
+
const url = new URL(this.iframe.src);
|
|
252
|
+
url.searchParams.set('theme', theme);
|
|
253
|
+
this.iframe.src = url.toString();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
isOpen() {
|
|
258
|
+
return this.isOpenState;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
on(event: string, handler: Function) {
|
|
262
|
+
if (!this.eventHandlers.has(event)) {
|
|
263
|
+
this.eventHandlers.set(event, []);
|
|
264
|
+
}
|
|
265
|
+
this.eventHandlers.get(event)!.push(handler);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private emit(event: string, ...args: any[]) {
|
|
269
|
+
const handlers = this.eventHandlers.get(event);
|
|
270
|
+
if (handlers) {
|
|
271
|
+
handlers.forEach(handler => {
|
|
272
|
+
try {
|
|
273
|
+
handler(...args);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.error(`[DoryDocs] Error in ${event} handler:`, error);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
destroy() {
|
|
282
|
+
// Cleanup event listeners
|
|
283
|
+
if (this.container && (this.container as any).__cleanup) {
|
|
284
|
+
(this.container as any).__cleanup();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Remove DOM elements
|
|
288
|
+
this.container?.remove();
|
|
289
|
+
this.overlay?.remove();
|
|
290
|
+
|
|
291
|
+
// Clear references
|
|
292
|
+
this.container = null;
|
|
293
|
+
this.iframe = null;
|
|
294
|
+
this.overlay = null;
|
|
295
|
+
this.eventHandlers.clear();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Expose widget globally for loader script
|
|
300
|
+
if (typeof window !== 'undefined') {
|
|
301
|
+
window.__DoryWidget__ = DoryWidget;
|
|
302
|
+
}
|
package/src/main.tsx
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { render } from 'preact'
|
|
2
|
+
import '@clidey/ux/styles.css';
|
|
3
|
+
import { App } from './app.tsx'
|
|
4
|
+
import { withMDX } from './components/mdx-provider.tsx'
|
|
5
|
+
import { loadFonts } from './utils/fonts'
|
|
6
|
+
import { loadColors } from './utils/colors'
|
|
7
|
+
import { Toaster } from '@clidey/ux'
|
|
8
|
+
import { ThemeProvider } from '@clidey/ux'
|
|
9
|
+
|
|
10
|
+
// Load fonts and colors before rendering the app
|
|
11
|
+
loadFonts()
|
|
12
|
+
loadColors()
|
|
13
|
+
|
|
14
|
+
const AppWithMDX = withMDX(App)
|
|
15
|
+
|
|
16
|
+
render(
|
|
17
|
+
<ThemeProvider storageKey="@clidey/dory/theme">
|
|
18
|
+
<Toaster />
|
|
19
|
+
<AppWithMDX />
|
|
20
|
+
</ThemeProvider>,
|
|
21
|
+
document.getElementById('app')!
|
|
22
|
+
)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Accordion as UXAccordion,
|
|
3
|
+
AccordionItem,
|
|
4
|
+
AccordionTrigger,
|
|
5
|
+
AccordionContent
|
|
6
|
+
} from '@clidey/ux'
|
|
7
|
+
import { Icon } from './icon'
|
|
8
|
+
|
|
9
|
+
export function AccordionGroup({ children }: { children: React.ReactNode }) {
|
|
10
|
+
return (
|
|
11
|
+
<UXAccordion type="single" collapsible className="my-6">
|
|
12
|
+
{children}
|
|
13
|
+
</UXAccordion>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function Accordion({
|
|
18
|
+
title,
|
|
19
|
+
children,
|
|
20
|
+
icon,
|
|
21
|
+
}: {
|
|
22
|
+
title: string
|
|
23
|
+
children: React.ReactNode
|
|
24
|
+
icon?: React.ComponentProps<typeof Icon>['icon']
|
|
25
|
+
}) {
|
|
26
|
+
return (
|
|
27
|
+
<AccordionItem value={title}>
|
|
28
|
+
<AccordionTrigger className="text-left">
|
|
29
|
+
<div className="flex items-center gap-2">
|
|
30
|
+
{icon && <Icon icon={icon} className="h-5 w-5" />}
|
|
31
|
+
<span className="text-base font-semibold">{title}</span>
|
|
32
|
+
</div>
|
|
33
|
+
</AccordionTrigger>
|
|
34
|
+
<AccordionContent>
|
|
35
|
+
<div className="text-base leading-relaxed">
|
|
36
|
+
{children}
|
|
37
|
+
</div>
|
|
38
|
+
</AccordionContent>
|
|
39
|
+
</AccordionItem>
|
|
40
|
+
)
|
|
41
|
+
}
|