@syntrologie/adapt-nav 0.0.0-semantically-released
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/dist/NavWidget.d.ts +31 -0
- package/dist/NavWidget.d.ts.map +1 -0
- package/dist/NavWidget.js +193 -0
- package/dist/cdn.d.ts +38 -0
- package/dist/cdn.d.ts.map +1 -0
- package/dist/cdn.js +36 -0
- package/dist/editor.d.ts +17 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +252 -0
- package/dist/runtime.d.ts +43 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +43 -0
- package/dist/schema.d.ts +647 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +101 -0
- package/dist/types.d.ts +121 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/package.json +49 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive Nav - NavWidget Component
|
|
3
|
+
*
|
|
4
|
+
* React component that renders a navigation link list with per-item
|
|
5
|
+
* conditional visibility based on showWhen decision strategies.
|
|
6
|
+
*
|
|
7
|
+
* Demonstrates the compositional action pattern where child actions
|
|
8
|
+
* (nav:link) serve as configuration data for the parent widget.
|
|
9
|
+
*/
|
|
10
|
+
import type { NavWidgetProps, NavConfig, NavWidgetRuntime } from './types';
|
|
11
|
+
/**
|
|
12
|
+
* NavWidget - Renders a navigation link list with per-item activation.
|
|
13
|
+
*
|
|
14
|
+
* This component demonstrates the compositional action pattern:
|
|
15
|
+
* - Parent (NavWidget) receives `config.actions` array
|
|
16
|
+
* - Each action has optional `showWhen` for per-item visibility
|
|
17
|
+
* - Parent evaluates showWhen and filters visible links
|
|
18
|
+
* - Parent manages re-rendering on context changes
|
|
19
|
+
*/
|
|
20
|
+
export declare function NavWidget({ config, runtime, instanceId }: NavWidgetProps): import("react/jsx-runtime").JSX.Element | null;
|
|
21
|
+
/**
|
|
22
|
+
* Mountable widget interface for the runtime's WidgetRegistry.
|
|
23
|
+
*/
|
|
24
|
+
export declare const NavMountableWidget: {
|
|
25
|
+
mount(container: HTMLElement, config?: NavConfig & {
|
|
26
|
+
runtime?: NavWidgetRuntime;
|
|
27
|
+
instanceId?: string;
|
|
28
|
+
}): () => void;
|
|
29
|
+
};
|
|
30
|
+
export default NavWidget;
|
|
31
|
+
//# sourceMappingURL=NavWidget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavWidget.d.ts","sourceRoot":"","sources":["../src/NavWidget.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAiB,SAAS,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AA8G1F;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,cAAc,kDAgFxE;AAMD;;GAEG;AACH,eAAO,MAAM,kBAAkB;qBAEhB,WAAW,WACb,SAAS,GAAG;QAAE,OAAO,CAAC,EAAE,gBAAgB,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;CAsC3E,CAAC;AAEF,eAAe,SAAS,CAAC"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Adaptive Nav - NavWidget Component
|
|
4
|
+
*
|
|
5
|
+
* React component that renders a navigation link list with per-item
|
|
6
|
+
* conditional visibility based on showWhen decision strategies.
|
|
7
|
+
*
|
|
8
|
+
* Demonstrates the compositional action pattern where child actions
|
|
9
|
+
* (nav:link) serve as configuration data for the parent widget.
|
|
10
|
+
*/
|
|
11
|
+
import React, { useEffect, useReducer, useMemo, useCallback } from 'react';
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Styles
|
|
14
|
+
// ============================================================================
|
|
15
|
+
const baseStyles = {
|
|
16
|
+
nav: {
|
|
17
|
+
display: 'flex',
|
|
18
|
+
gap: '4px',
|
|
19
|
+
padding: '8px',
|
|
20
|
+
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
21
|
+
},
|
|
22
|
+
link: {
|
|
23
|
+
display: 'flex',
|
|
24
|
+
alignItems: 'center',
|
|
25
|
+
gap: '6px',
|
|
26
|
+
padding: '8px 12px',
|
|
27
|
+
borderRadius: '6px',
|
|
28
|
+
textDecoration: 'none',
|
|
29
|
+
fontSize: '14px',
|
|
30
|
+
fontWeight: 500,
|
|
31
|
+
transition: 'background-color 0.15s ease, color 0.15s ease',
|
|
32
|
+
cursor: 'pointer',
|
|
33
|
+
border: 'none',
|
|
34
|
+
background: 'transparent',
|
|
35
|
+
},
|
|
36
|
+
icon: {
|
|
37
|
+
fontSize: '16px',
|
|
38
|
+
},
|
|
39
|
+
externalIcon: {
|
|
40
|
+
fontSize: '12px',
|
|
41
|
+
opacity: 0.6,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
const themeStyles = {
|
|
45
|
+
light: {
|
|
46
|
+
nav: {
|
|
47
|
+
backgroundColor: '#ffffff',
|
|
48
|
+
},
|
|
49
|
+
link: {
|
|
50
|
+
color: '#374151',
|
|
51
|
+
},
|
|
52
|
+
linkHover: {
|
|
53
|
+
backgroundColor: '#f3f4f6',
|
|
54
|
+
color: '#111827',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
dark: {
|
|
58
|
+
nav: {
|
|
59
|
+
backgroundColor: '#1f2937',
|
|
60
|
+
},
|
|
61
|
+
link: {
|
|
62
|
+
color: '#d1d5db',
|
|
63
|
+
},
|
|
64
|
+
linkHover: {
|
|
65
|
+
backgroundColor: '#374151',
|
|
66
|
+
color: '#f9fafb',
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
function NavLinkComponent({ link, theme, onNavigate }) {
|
|
71
|
+
const [isHovered, setIsHovered] = React.useState(false);
|
|
72
|
+
const { label, href, icon, external } = link.config;
|
|
73
|
+
const colors = themeStyles[theme];
|
|
74
|
+
const style = {
|
|
75
|
+
...baseStyles.link,
|
|
76
|
+
...colors.link,
|
|
77
|
+
...(isHovered ? colors.linkHover : {}),
|
|
78
|
+
};
|
|
79
|
+
const handleClick = (e) => {
|
|
80
|
+
e.preventDefault();
|
|
81
|
+
onNavigate(href, external ?? false);
|
|
82
|
+
};
|
|
83
|
+
return (_jsxs("a", { href: href, onClick: handleClick, style: style, onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), target: external ? '_blank' : undefined, rel: external ? 'noopener noreferrer' : undefined, children: [icon && _jsx("span", { style: baseStyles.icon, children: icon }), _jsx("span", { children: label }), external && _jsx("span", { style: baseStyles.externalIcon, children: "\u2197" })] }));
|
|
84
|
+
}
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// NavWidget Component
|
|
87
|
+
// ============================================================================
|
|
88
|
+
/**
|
|
89
|
+
* NavWidget - Renders a navigation link list with per-item activation.
|
|
90
|
+
*
|
|
91
|
+
* This component demonstrates the compositional action pattern:
|
|
92
|
+
* - Parent (NavWidget) receives `config.actions` array
|
|
93
|
+
* - Each action has optional `showWhen` for per-item visibility
|
|
94
|
+
* - Parent evaluates showWhen and filters visible links
|
|
95
|
+
* - Parent manages re-rendering on context changes
|
|
96
|
+
*/
|
|
97
|
+
export function NavWidget({ config, runtime, instanceId }) {
|
|
98
|
+
// Force re-render when context changes
|
|
99
|
+
const [, forceUpdate] = useReducer((x) => x + 1, 0);
|
|
100
|
+
// Subscribe to context changes for reactive updates
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
const unsubscribe = runtime.context.subscribe(() => {
|
|
103
|
+
forceUpdate();
|
|
104
|
+
});
|
|
105
|
+
return unsubscribe;
|
|
106
|
+
}, [runtime.context]);
|
|
107
|
+
// Filter visible links based on per-item showWhen
|
|
108
|
+
const visibleLinks = useMemo(() => {
|
|
109
|
+
return config.actions.filter((link) => {
|
|
110
|
+
// No showWhen = always visible
|
|
111
|
+
if (!link.showWhen)
|
|
112
|
+
return true;
|
|
113
|
+
// Evaluate the decision strategy
|
|
114
|
+
const result = runtime.evaluateSync(link.showWhen);
|
|
115
|
+
return result.value;
|
|
116
|
+
});
|
|
117
|
+
}, [config.actions, runtime]);
|
|
118
|
+
// Resolve theme (auto → detect system preference)
|
|
119
|
+
const resolvedTheme = useMemo(() => {
|
|
120
|
+
if (config.theme !== 'auto')
|
|
121
|
+
return config.theme;
|
|
122
|
+
// Check system preference (SSR-safe)
|
|
123
|
+
if (typeof window !== 'undefined') {
|
|
124
|
+
return window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
125
|
+
}
|
|
126
|
+
return 'light';
|
|
127
|
+
}, [config.theme]);
|
|
128
|
+
// Handle navigation with event publishing
|
|
129
|
+
const handleNavigate = useCallback((href, external) => {
|
|
130
|
+
// Publish navigation event for analytics
|
|
131
|
+
runtime.events.publish('nav:click', {
|
|
132
|
+
instanceId,
|
|
133
|
+
href,
|
|
134
|
+
external,
|
|
135
|
+
timestamp: Date.now(),
|
|
136
|
+
});
|
|
137
|
+
// Perform navigation
|
|
138
|
+
if (external) {
|
|
139
|
+
window.open(href, '_blank', 'noopener,noreferrer');
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
window.location.href = href;
|
|
143
|
+
}
|
|
144
|
+
}, [runtime.events, instanceId]);
|
|
145
|
+
// Compute nav styles
|
|
146
|
+
const navStyle = {
|
|
147
|
+
...baseStyles.nav,
|
|
148
|
+
...themeStyles[resolvedTheme].nav,
|
|
149
|
+
flexDirection: config.layout === 'vertical' ? 'column' : 'row',
|
|
150
|
+
};
|
|
151
|
+
// Empty state
|
|
152
|
+
if (visibleLinks.length === 0) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
return (_jsx("nav", { style: navStyle, "data-adaptive-id": instanceId, "data-adaptive-type": "adaptive-nav", children: visibleLinks.map((link, index) => (_jsx(NavLinkComponent, { link: link, theme: resolvedTheme, onNavigate: handleNavigate }, link.config.href + index))) }));
|
|
156
|
+
}
|
|
157
|
+
// ============================================================================
|
|
158
|
+
// Mountable Widget Interface
|
|
159
|
+
// ============================================================================
|
|
160
|
+
/**
|
|
161
|
+
* Mountable widget interface for the runtime's WidgetRegistry.
|
|
162
|
+
*/
|
|
163
|
+
export const NavMountableWidget = {
|
|
164
|
+
mount(container, config) {
|
|
165
|
+
// This is a simplified mount for non-React environments
|
|
166
|
+
// In practice, the runtime handles React rendering
|
|
167
|
+
const { runtime, instanceId: _instanceId = 'nav-widget', ...navConfig } = config || {
|
|
168
|
+
layout: 'horizontal',
|
|
169
|
+
theme: 'auto',
|
|
170
|
+
actions: [],
|
|
171
|
+
};
|
|
172
|
+
// Create simple HTML fallback if no runtime
|
|
173
|
+
if (!runtime) {
|
|
174
|
+
const links = navConfig.actions || [];
|
|
175
|
+
container.innerHTML = `
|
|
176
|
+
<nav style="display: flex; gap: 8px; padding: 8px; font-family: system-ui;">
|
|
177
|
+
${links
|
|
178
|
+
.map((link) => `
|
|
179
|
+
<a href="${link.config.href}" style="padding: 8px 12px; text-decoration: none; color: #374151;">
|
|
180
|
+
${link.config.icon ? `<span>${link.config.icon}</span>` : ''}
|
|
181
|
+
${link.config.label}
|
|
182
|
+
</a>
|
|
183
|
+
`)
|
|
184
|
+
.join('')}
|
|
185
|
+
</nav>
|
|
186
|
+
`;
|
|
187
|
+
}
|
|
188
|
+
return () => {
|
|
189
|
+
container.innerHTML = '';
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
export default NavWidget;
|
package/dist/cdn.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDN Entry Point for Adaptive Nav
|
|
3
|
+
*
|
|
4
|
+
* This module is bundled for CDN delivery and self-registers with the global
|
|
5
|
+
* SynOS app registry when loaded dynamically via the AppLoader.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* App manifest for registry registration.
|
|
9
|
+
* Follows the AppManifest interface expected by AppLoader/AppRegistry.
|
|
10
|
+
*/
|
|
11
|
+
export declare const manifest: {
|
|
12
|
+
id: string;
|
|
13
|
+
version: string;
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
runtime: {
|
|
17
|
+
actions: never[];
|
|
18
|
+
widgets: {
|
|
19
|
+
id: string;
|
|
20
|
+
component: {
|
|
21
|
+
mount(container: HTMLElement, config?: import("./types").NavConfig & {
|
|
22
|
+
runtime?: import("./types").NavWidgetRuntime;
|
|
23
|
+
instanceId?: string;
|
|
24
|
+
}): () => void;
|
|
25
|
+
};
|
|
26
|
+
metadata: {
|
|
27
|
+
name: string;
|
|
28
|
+
description: string;
|
|
29
|
+
icon: string;
|
|
30
|
+
};
|
|
31
|
+
}[];
|
|
32
|
+
};
|
|
33
|
+
metadata: {
|
|
34
|
+
isBuiltIn: boolean;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export default manifest;
|
|
38
|
+
//# sourceMappingURL=cdn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cdn.d.ts","sourceRoot":"","sources":["../src/cdn.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH;;;GAGG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;;2BA2B+iK,CAAC;8BAA8B,CAAC;;;;;;;;;;;;;CAdnmK,CAAC;AAaF,eAAe,QAAQ,CAAC"}
|
package/dist/cdn.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDN Entry Point for Adaptive Nav
|
|
3
|
+
*
|
|
4
|
+
* This module is bundled for CDN delivery and self-registers with the global
|
|
5
|
+
* SynOS app registry when loaded dynamically via the AppLoader.
|
|
6
|
+
*/
|
|
7
|
+
import { runtime } from './runtime';
|
|
8
|
+
/**
|
|
9
|
+
* App manifest for registry registration.
|
|
10
|
+
* Follows the AppManifest interface expected by AppLoader/AppRegistry.
|
|
11
|
+
*/
|
|
12
|
+
export const manifest = {
|
|
13
|
+
id: 'nav',
|
|
14
|
+
version: runtime.version,
|
|
15
|
+
name: runtime.name,
|
|
16
|
+
description: runtime.description,
|
|
17
|
+
runtime: {
|
|
18
|
+
// Nav is widget-based, no action executors
|
|
19
|
+
actions: [],
|
|
20
|
+
widgets: runtime.widgets,
|
|
21
|
+
},
|
|
22
|
+
metadata: {
|
|
23
|
+
isBuiltIn: false,
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Self-register with global registry if available.
|
|
28
|
+
* This happens when loaded via script tag (UMD).
|
|
29
|
+
*/
|
|
30
|
+
if (typeof window !== 'undefined') {
|
|
31
|
+
const globalRegistry = window.__SYNOS_APP_REGISTRY__;
|
|
32
|
+
if (globalRegistry && typeof globalRegistry.register === 'function') {
|
|
33
|
+
globalRegistry.register(manifest);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export default manifest;
|
package/dist/editor.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive Nav - Editor Component
|
|
3
|
+
*
|
|
4
|
+
* Visual editor panel for configuring navigation links.
|
|
5
|
+
*/
|
|
6
|
+
import type { EditorPanelProps } from './types';
|
|
7
|
+
export declare function NavEditor({ config, onChange, editor }: EditorPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
/**
|
|
9
|
+
* Editor panel configuration for the app registry.
|
|
10
|
+
*/
|
|
11
|
+
export declare const editorPanel: {
|
|
12
|
+
title: string;
|
|
13
|
+
icon: string;
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
16
|
+
export default NavEditor;
|
|
17
|
+
//# sourceMappingURL=editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../src/editor.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAqLhD,wBAAgB,SAAS,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,gBAAgB,2CAkMvE;AAED;;GAEG;AACH,eAAO,MAAM,WAAW;;;;CAIvB,CAAC;AAEF,eAAe,SAAS,CAAC"}
|
package/dist/editor.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Editor Styles
|
|
4
|
+
// ============================================================================
|
|
5
|
+
const styles = {
|
|
6
|
+
container: {
|
|
7
|
+
display: 'flex',
|
|
8
|
+
flexDirection: 'column',
|
|
9
|
+
height: '100%',
|
|
10
|
+
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
11
|
+
},
|
|
12
|
+
header: {
|
|
13
|
+
padding: '16px',
|
|
14
|
+
borderBottom: '1px solid #334155',
|
|
15
|
+
display: 'flex',
|
|
16
|
+
alignItems: 'center',
|
|
17
|
+
gap: '12px',
|
|
18
|
+
},
|
|
19
|
+
backButton: {
|
|
20
|
+
padding: '6px 12px',
|
|
21
|
+
borderRadius: '6px',
|
|
22
|
+
border: 'none',
|
|
23
|
+
backgroundColor: 'rgba(255,255,255,0.05)',
|
|
24
|
+
color: '#94a3b8',
|
|
25
|
+
fontSize: '13px',
|
|
26
|
+
cursor: 'pointer',
|
|
27
|
+
},
|
|
28
|
+
title: {
|
|
29
|
+
margin: 0,
|
|
30
|
+
fontSize: '15px',
|
|
31
|
+
fontWeight: 600,
|
|
32
|
+
color: '#f8fafc',
|
|
33
|
+
},
|
|
34
|
+
subtitle: {
|
|
35
|
+
margin: '2px 0 0 0',
|
|
36
|
+
fontSize: '11px',
|
|
37
|
+
color: '#64748b',
|
|
38
|
+
},
|
|
39
|
+
content: {
|
|
40
|
+
flex: 1,
|
|
41
|
+
overflow: 'auto',
|
|
42
|
+
padding: '16px',
|
|
43
|
+
},
|
|
44
|
+
section: {
|
|
45
|
+
marginBottom: '24px',
|
|
46
|
+
},
|
|
47
|
+
sectionTitle: {
|
|
48
|
+
fontSize: '12px',
|
|
49
|
+
fontWeight: 600,
|
|
50
|
+
color: '#94a3b8',
|
|
51
|
+
textTransform: 'uppercase',
|
|
52
|
+
letterSpacing: '0.05em',
|
|
53
|
+
marginBottom: '12px',
|
|
54
|
+
},
|
|
55
|
+
select: {
|
|
56
|
+
width: '100%',
|
|
57
|
+
padding: '10px 12px',
|
|
58
|
+
borderRadius: '6px',
|
|
59
|
+
border: '1px solid rgba(255,255,255,0.1)',
|
|
60
|
+
backgroundColor: 'rgba(255,255,255,0.05)',
|
|
61
|
+
color: '#f8fafc',
|
|
62
|
+
fontSize: '13px',
|
|
63
|
+
cursor: 'pointer',
|
|
64
|
+
},
|
|
65
|
+
addButton: {
|
|
66
|
+
width: '100%',
|
|
67
|
+
padding: '12px',
|
|
68
|
+
borderRadius: '8px',
|
|
69
|
+
border: '1px dashed rgba(59, 130, 246, 0.3)',
|
|
70
|
+
background: 'rgba(59, 130, 246, 0.05)',
|
|
71
|
+
color: '#3b82f6',
|
|
72
|
+
fontSize: '13px',
|
|
73
|
+
fontWeight: 600,
|
|
74
|
+
cursor: 'pointer',
|
|
75
|
+
marginBottom: '16px',
|
|
76
|
+
},
|
|
77
|
+
linkCard: {
|
|
78
|
+
padding: '12px',
|
|
79
|
+
borderRadius: '8px',
|
|
80
|
+
border: '1px solid rgba(255,255,255,0.08)',
|
|
81
|
+
background: 'rgba(255,255,255,0.02)',
|
|
82
|
+
marginBottom: '8px',
|
|
83
|
+
},
|
|
84
|
+
linkHeader: {
|
|
85
|
+
display: 'flex',
|
|
86
|
+
alignItems: 'center',
|
|
87
|
+
gap: '12px',
|
|
88
|
+
marginBottom: '12px',
|
|
89
|
+
},
|
|
90
|
+
linkIcon: {
|
|
91
|
+
fontSize: '20px',
|
|
92
|
+
},
|
|
93
|
+
linkInfo: {
|
|
94
|
+
flex: 1,
|
|
95
|
+
},
|
|
96
|
+
linkLabel: {
|
|
97
|
+
fontSize: '14px',
|
|
98
|
+
fontWeight: 500,
|
|
99
|
+
color: '#f8fafc',
|
|
100
|
+
},
|
|
101
|
+
linkHref: {
|
|
102
|
+
fontSize: '12px',
|
|
103
|
+
color: '#64748b',
|
|
104
|
+
},
|
|
105
|
+
removeButton: {
|
|
106
|
+
padding: '4px 8px',
|
|
107
|
+
borderRadius: '4px',
|
|
108
|
+
border: 'none',
|
|
109
|
+
background: 'rgba(239, 68, 68, 0.1)',
|
|
110
|
+
color: '#ef4444',
|
|
111
|
+
fontSize: '12px',
|
|
112
|
+
cursor: 'pointer',
|
|
113
|
+
},
|
|
114
|
+
inputGroup: {
|
|
115
|
+
marginBottom: '8px',
|
|
116
|
+
},
|
|
117
|
+
inputLabel: {
|
|
118
|
+
display: 'block',
|
|
119
|
+
fontSize: '11px',
|
|
120
|
+
color: '#94a3b8',
|
|
121
|
+
marginBottom: '4px',
|
|
122
|
+
},
|
|
123
|
+
input: {
|
|
124
|
+
width: '100%',
|
|
125
|
+
padding: '8px 10px',
|
|
126
|
+
borderRadius: '4px',
|
|
127
|
+
border: '1px solid rgba(255,255,255,0.1)',
|
|
128
|
+
backgroundColor: 'rgba(255,255,255,0.05)',
|
|
129
|
+
color: '#f8fafc',
|
|
130
|
+
fontSize: '13px',
|
|
131
|
+
},
|
|
132
|
+
checkbox: {
|
|
133
|
+
display: 'flex',
|
|
134
|
+
alignItems: 'center',
|
|
135
|
+
gap: '8px',
|
|
136
|
+
fontSize: '13px',
|
|
137
|
+
color: '#d1d5db',
|
|
138
|
+
},
|
|
139
|
+
emptyState: {
|
|
140
|
+
textAlign: 'center',
|
|
141
|
+
padding: '32px 16px',
|
|
142
|
+
color: '#64748b',
|
|
143
|
+
fontSize: '13px',
|
|
144
|
+
},
|
|
145
|
+
footer: {
|
|
146
|
+
padding: '12px 16px',
|
|
147
|
+
borderTop: '1px solid #334155',
|
|
148
|
+
display: 'flex',
|
|
149
|
+
gap: '8px',
|
|
150
|
+
},
|
|
151
|
+
saveButton: {
|
|
152
|
+
flex: 1,
|
|
153
|
+
padding: '10px',
|
|
154
|
+
borderRadius: '8px',
|
|
155
|
+
border: 'none',
|
|
156
|
+
background: 'rgba(59, 130, 246, 0.15)',
|
|
157
|
+
color: '#3b82f6',
|
|
158
|
+
fontSize: '13px',
|
|
159
|
+
fontWeight: 600,
|
|
160
|
+
cursor: 'pointer',
|
|
161
|
+
},
|
|
162
|
+
publishButton: {
|
|
163
|
+
flex: 1,
|
|
164
|
+
padding: '10px',
|
|
165
|
+
borderRadius: '8px',
|
|
166
|
+
border: 'none',
|
|
167
|
+
background: '#22c55e',
|
|
168
|
+
color: 'white',
|
|
169
|
+
fontSize: '13px',
|
|
170
|
+
fontWeight: 600,
|
|
171
|
+
cursor: 'pointer',
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
// ============================================================================
|
|
175
|
+
// NavEditor Component
|
|
176
|
+
// ============================================================================
|
|
177
|
+
export function NavEditor({ config, onChange, editor }) {
|
|
178
|
+
const typedConfig = config;
|
|
179
|
+
const links = typedConfig.actions || [];
|
|
180
|
+
// Add a new link
|
|
181
|
+
const handleAddLink = () => {
|
|
182
|
+
const newLink = {
|
|
183
|
+
kind: 'nav:link',
|
|
184
|
+
config: {
|
|
185
|
+
label: 'New Link',
|
|
186
|
+
href: '/',
|
|
187
|
+
icon: '🔗',
|
|
188
|
+
external: false,
|
|
189
|
+
},
|
|
190
|
+
showWhen: null,
|
|
191
|
+
};
|
|
192
|
+
onChange({
|
|
193
|
+
...config,
|
|
194
|
+
actions: [...links, newLink],
|
|
195
|
+
});
|
|
196
|
+
editor.setDirty(true);
|
|
197
|
+
};
|
|
198
|
+
// Remove a link
|
|
199
|
+
const handleRemoveLink = (index) => {
|
|
200
|
+
const newLinks = links.filter((_, i) => i !== index);
|
|
201
|
+
onChange({
|
|
202
|
+
...config,
|
|
203
|
+
actions: newLinks,
|
|
204
|
+
});
|
|
205
|
+
editor.setDirty(true);
|
|
206
|
+
};
|
|
207
|
+
// Update a link
|
|
208
|
+
const handleUpdateLink = (index, updates) => {
|
|
209
|
+
const newLinks = links.map((link, i) => {
|
|
210
|
+
if (i !== index)
|
|
211
|
+
return link;
|
|
212
|
+
return {
|
|
213
|
+
...link,
|
|
214
|
+
config: {
|
|
215
|
+
...link.config,
|
|
216
|
+
...updates,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
onChange({
|
|
221
|
+
...config,
|
|
222
|
+
actions: newLinks,
|
|
223
|
+
});
|
|
224
|
+
editor.setDirty(true);
|
|
225
|
+
};
|
|
226
|
+
// Update layout
|
|
227
|
+
const handleLayoutChange = (e) => {
|
|
228
|
+
onChange({
|
|
229
|
+
...config,
|
|
230
|
+
layout: e.target.value,
|
|
231
|
+
});
|
|
232
|
+
editor.setDirty(true);
|
|
233
|
+
};
|
|
234
|
+
// Update theme
|
|
235
|
+
const handleThemeChange = (e) => {
|
|
236
|
+
onChange({
|
|
237
|
+
...config,
|
|
238
|
+
theme: e.target.value,
|
|
239
|
+
});
|
|
240
|
+
editor.setDirty(true);
|
|
241
|
+
};
|
|
242
|
+
return (_jsxs("div", { style: styles.container, children: [_jsxs("div", { style: styles.header, children: [_jsx("button", { onClick: () => editor.navigateHome(), style: styles.backButton, children: "\u2190 Back" }), _jsxs("div", { children: [_jsx("h2", { style: styles.title, children: "Navigation Links" }), _jsx("p", { style: styles.subtitle, children: "Configure navigation with per-item visibility" })] })] }), _jsxs("div", { style: styles.content, children: [_jsxs("div", { style: styles.section, children: [_jsx("div", { style: styles.sectionTitle, children: "Layout" }), _jsxs("select", { value: typedConfig.layout || 'horizontal', onChange: handleLayoutChange, style: styles.select, children: [_jsx("option", { value: "horizontal", children: "Horizontal" }), _jsx("option", { value: "vertical", children: "Vertical" })] })] }), _jsxs("div", { style: styles.section, children: [_jsx("div", { style: styles.sectionTitle, children: "Theme" }), _jsxs("select", { value: typedConfig.theme || 'auto', onChange: handleThemeChange, style: styles.select, children: [_jsx("option", { value: "auto", children: "Auto (System)" }), _jsx("option", { value: "light", children: "Light" }), _jsx("option", { value: "dark", children: "Dark" })] })] }), _jsxs("div", { style: styles.section, children: [_jsx("div", { style: styles.sectionTitle, children: "Navigation Links" }), _jsx("button", { onClick: handleAddLink, style: styles.addButton, children: "+ Add Link" }), links.map((link, index) => (_jsxs("div", { style: styles.linkCard, children: [_jsxs("div", { style: styles.linkHeader, children: [_jsx("span", { style: styles.linkIcon, children: link.config.icon || '🔗' }), _jsxs("div", { style: styles.linkInfo, children: [_jsx("div", { style: styles.linkLabel, children: link.config.label }), _jsx("div", { style: styles.linkHref, children: link.config.href })] }), _jsx("button", { onClick: () => handleRemoveLink(index), style: styles.removeButton, children: "Remove" })] }), _jsxs("div", { style: styles.inputGroup, children: [_jsx("label", { style: styles.inputLabel, children: "Label" }), _jsx("input", { type: "text", value: link.config.label, onChange: (e) => handleUpdateLink(index, { label: e.target.value }), style: styles.input })] }), _jsxs("div", { style: styles.inputGroup, children: [_jsx("label", { style: styles.inputLabel, children: "URL" }), _jsx("input", { type: "text", value: link.config.href, onChange: (e) => handleUpdateLink(index, { href: e.target.value }), style: styles.input })] }), _jsxs("div", { style: styles.inputGroup, children: [_jsx("label", { style: styles.inputLabel, children: "Icon (emoji)" }), _jsx("input", { type: "text", value: link.config.icon || '', onChange: (e) => handleUpdateLink(index, { icon: e.target.value }), style: styles.input, placeholder: "e.g., \uD83C\uDFE0" })] }), _jsxs("label", { style: styles.checkbox, children: [_jsx("input", { type: "checkbox", checked: link.config.external || false, onChange: (e) => handleUpdateLink(index, { external: e.target.checked }) }), "Open in new tab"] })] }, index))), links.length === 0 && (_jsx("div", { style: styles.emptyState, children: "No links configured. Click the button above to add one." }))] })] }), _jsxs("div", { style: styles.footer, children: [_jsx("button", { onClick: () => editor.save(), style: styles.saveButton, children: "Save Draft" }), _jsx("button", { onClick: () => editor.publish(), style: styles.publishButton, children: "Publish" })] })] }));
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Editor panel configuration for the app registry.
|
|
246
|
+
*/
|
|
247
|
+
export const editorPanel = {
|
|
248
|
+
title: 'Navigation',
|
|
249
|
+
icon: '🔗',
|
|
250
|
+
description: 'Navigation link list with per-item visibility',
|
|
251
|
+
};
|
|
252
|
+
export default NavEditor;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive Nav - Runtime Module
|
|
3
|
+
*
|
|
4
|
+
* Runtime manifest for the navigation link list adaptive.
|
|
5
|
+
* This is a widget-based adaptive with no action executors.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Runtime manifest for adaptive-nav.
|
|
9
|
+
*
|
|
10
|
+
* Note: This adaptive is widget-based, not action-based.
|
|
11
|
+
* The `nav:link` actions are compositional - they're rendered by
|
|
12
|
+
* the widget, not executed by the runtime.
|
|
13
|
+
*/
|
|
14
|
+
export declare const runtime: {
|
|
15
|
+
id: string;
|
|
16
|
+
version: string;
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
/**
|
|
20
|
+
* No action executors - nav:link actions are compositional,
|
|
21
|
+
* meaning they serve as configuration for the NavWidget.
|
|
22
|
+
*/
|
|
23
|
+
executors: never[];
|
|
24
|
+
/**
|
|
25
|
+
* Widget definitions for the runtime's WidgetRegistry.
|
|
26
|
+
*/
|
|
27
|
+
widgets: {
|
|
28
|
+
id: string;
|
|
29
|
+
component: {
|
|
30
|
+
mount(container: HTMLElement, config?: import("./types").NavConfig & {
|
|
31
|
+
runtime?: import("./types").NavWidgetRuntime;
|
|
32
|
+
instanceId?: string;
|
|
33
|
+
}): () => void;
|
|
34
|
+
};
|
|
35
|
+
metadata: {
|
|
36
|
+
name: string;
|
|
37
|
+
description: string;
|
|
38
|
+
icon: string;
|
|
39
|
+
};
|
|
40
|
+
}[];
|
|
41
|
+
};
|
|
42
|
+
export default runtime;
|
|
43
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH;;;;;;GAMG;AACH,eAAO,MAAM,OAAO;;;;;IAMlB;;;OAGG;;IAGH;;OAEG;;;;;uBAeuwJ,CAAC;0BAA8B,CAAC;;;;;;;;;CAH3yJ,CAAC;AAEF,eAAe,OAAO,CAAC"}
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive Nav - Runtime Module
|
|
3
|
+
*
|
|
4
|
+
* Runtime manifest for the navigation link list adaptive.
|
|
5
|
+
* This is a widget-based adaptive with no action executors.
|
|
6
|
+
*/
|
|
7
|
+
import { NavMountableWidget } from './NavWidget';
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// App Runtime Manifest
|
|
10
|
+
// ============================================================================
|
|
11
|
+
/**
|
|
12
|
+
* Runtime manifest for adaptive-nav.
|
|
13
|
+
*
|
|
14
|
+
* Note: This adaptive is widget-based, not action-based.
|
|
15
|
+
* The `nav:link` actions are compositional - they're rendered by
|
|
16
|
+
* the widget, not executed by the runtime.
|
|
17
|
+
*/
|
|
18
|
+
export const runtime = {
|
|
19
|
+
id: 'adaptive-nav',
|
|
20
|
+
version: '1.0.0',
|
|
21
|
+
name: 'Navigation Links',
|
|
22
|
+
description: 'Widget-based navigation link list with per-item conditional visibility',
|
|
23
|
+
/**
|
|
24
|
+
* No action executors - nav:link actions are compositional,
|
|
25
|
+
* meaning they serve as configuration for the NavWidget.
|
|
26
|
+
*/
|
|
27
|
+
executors: [],
|
|
28
|
+
/**
|
|
29
|
+
* Widget definitions for the runtime's WidgetRegistry.
|
|
30
|
+
*/
|
|
31
|
+
widgets: [
|
|
32
|
+
{
|
|
33
|
+
id: 'adaptive-nav:links',
|
|
34
|
+
component: NavMountableWidget,
|
|
35
|
+
metadata: {
|
|
36
|
+
name: 'Navigation Links',
|
|
37
|
+
description: 'Horizontal or vertical navigation link list',
|
|
38
|
+
icon: '🔗',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
export default runtime;
|