@nitrostack/widgets 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/use-display-mode.d.ts +11 -0
- package/dist/hooks/use-display-mode.d.ts.map +1 -0
- package/dist/hooks/use-display-mode.js +12 -0
- package/dist/hooks/use-max-height.d.ts +10 -0
- package/dist/hooks/use-max-height.d.ts.map +1 -0
- package/dist/hooks/use-max-height.js +12 -0
- package/dist/hooks/use-openai-global.d.ts +12 -0
- package/dist/hooks/use-openai-global.d.ts.map +1 -0
- package/dist/hooks/use-openai-global.js +31 -0
- package/dist/hooks/use-theme.d.ts +10 -0
- package/dist/hooks/use-theme.d.ts.map +1 -0
- package/dist/hooks/use-theme.js +11 -0
- package/dist/hooks/use-widget-state.d.ts +18 -0
- package/dist/hooks/use-widget-state.d.ts.map +1 -0
- package/dist/hooks/use-widget-state.js +26 -0
- package/dist/hooks/useWidgetSDK.d.ts +49 -0
- package/dist/hooks/useWidgetSDK.d.ts.map +1 -0
- package/dist/hooks/useWidgetSDK.js +69 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/metadata.d.ts +53 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +28 -0
- package/dist/runtime/WidgetLayout.d.ts +32 -0
- package/dist/runtime/WidgetLayout.d.ts.map +1 -0
- package/dist/runtime/WidgetLayout.js +143 -0
- package/dist/runtime/widget-polyfill.d.ts +2 -0
- package/dist/runtime/widget-polyfill.d.ts.map +1 -0
- package/dist/runtime/widget-polyfill.js +27 -0
- package/dist/sdk.d.ts +117 -0
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +232 -0
- package/dist/types.d.ts +89 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/utils/media-queries.d.ts +34 -0
- package/dist/utils/media-queries.d.ts.map +1 -0
- package/dist/utils/media-queries.js +41 -0
- package/dist/withToolData.d.ts +19 -0
- package/dist/withToolData.d.ts.map +1 -0
- package/dist/withToolData.js +227 -0
- package/package.json +64 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media query utilities for responsive and accessible widget design
|
|
3
|
+
*/
|
|
4
|
+
function matchMediaQuery(query) {
|
|
5
|
+
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function')
|
|
6
|
+
return false;
|
|
7
|
+
return window.matchMedia(query).matches;
|
|
8
|
+
}
|
|
9
|
+
function createMediaQueryFn(query) {
|
|
10
|
+
return () => matchMediaQuery(query);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Check if user prefers reduced motion
|
|
14
|
+
* Use this to disable animations for accessibility
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const shouldAnimate = !prefersReducedMotion();
|
|
18
|
+
*/
|
|
19
|
+
export const prefersReducedMotion = createMediaQueryFn('(prefers-reduced-motion: reduce)');
|
|
20
|
+
/**
|
|
21
|
+
* Check if device is primarily touch-based
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const isTouchDevice = isPrimarilyTouchDevice();
|
|
25
|
+
*/
|
|
26
|
+
export const isPrimarilyTouchDevice = createMediaQueryFn('(pointer: coarse)');
|
|
27
|
+
/**
|
|
28
|
+
* Check if hover is available
|
|
29
|
+
* Use this to conditionally show hover states
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const canHover = isHoverAvailable();
|
|
33
|
+
*/
|
|
34
|
+
export const isHoverAvailable = createMediaQueryFn('(hover: hover)');
|
|
35
|
+
/**
|
|
36
|
+
* Check if user prefers dark color scheme
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* const prefersDark = prefersDarkColorScheme();
|
|
40
|
+
*/
|
|
41
|
+
export const prefersDarkColorScheme = createMediaQueryFn('(prefers-color-scheme: dark)');
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Higher-Order Component for Next.js widgets
|
|
4
|
+
* Automatically handles data fetching from window.openai.toolOutput
|
|
5
|
+
* Eliminates boilerplate code across all widgets
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { withToolData } from 'nitrostack/widgets';
|
|
9
|
+
* export default withToolData<YourDataType>(YourComponent);
|
|
10
|
+
*/
|
|
11
|
+
export interface ToolOutputWrapper<T = any> {
|
|
12
|
+
data: T | null;
|
|
13
|
+
loading: boolean;
|
|
14
|
+
error: string | null;
|
|
15
|
+
}
|
|
16
|
+
export declare function withToolData<T = any>(WrappedComponent: React.ComponentType<{
|
|
17
|
+
data: T;
|
|
18
|
+
}>): () => import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
//# sourceMappingURL=withToolData.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"withToolData.d.ts","sourceRoot":"","sources":["../src/withToolData.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD;;;;;;;;GAQG;AAEH,MAAM,WAAW,iBAAiB,CAAC,CAAC,GAAG,GAAG;IACxC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,wBAAgB,YAAY,CAAC,CAAC,GAAG,GAAG,EAClC,gBAAgB,EAAE,KAAK,CAAC,aAAa,CAAC;IAAE,IAAI,EAAE,CAAC,CAAA;CAAE,CAAC,iDAkNnD"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import React, { useEffect, useState } from 'react';
|
|
4
|
+
export function withToolData(WrappedComponent) {
|
|
5
|
+
return function WithToolDataComponent() {
|
|
6
|
+
const [state, setState] = useState({
|
|
7
|
+
data: null,
|
|
8
|
+
loading: true,
|
|
9
|
+
error: null,
|
|
10
|
+
});
|
|
11
|
+
const [mounted, setMounted] = useState(false);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
// Mark as mounted to prevent SSR/hydration issues
|
|
14
|
+
setMounted(true);
|
|
15
|
+
// Function to check for data
|
|
16
|
+
const checkForData = () => {
|
|
17
|
+
try {
|
|
18
|
+
if (typeof window !== 'undefined') {
|
|
19
|
+
const openai = window.openai;
|
|
20
|
+
if (openai && openai.toolOutput) {
|
|
21
|
+
setState({
|
|
22
|
+
data: openai.toolOutput,
|
|
23
|
+
loading: false,
|
|
24
|
+
error: null,
|
|
25
|
+
});
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
setState({
|
|
32
|
+
data: null,
|
|
33
|
+
loading: false,
|
|
34
|
+
error: err instanceof Error ? err.message : 'Unknown error',
|
|
35
|
+
});
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
};
|
|
40
|
+
// Check immediately
|
|
41
|
+
if (checkForData()) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
;
|
|
45
|
+
// Listen for postMessage (for dev mode)
|
|
46
|
+
const handleMessage = (event) => {
|
|
47
|
+
console.log('[Widget] Received postMessage:', event.data);
|
|
48
|
+
if (event.data && event.data.type === 'toolOutput') {
|
|
49
|
+
console.log('[Widget] Setting data:', event.data.data);
|
|
50
|
+
setState({
|
|
51
|
+
data: event.data.data,
|
|
52
|
+
loading: false,
|
|
53
|
+
error: null,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
window.addEventListener('message', handleMessage);
|
|
58
|
+
// Fallback: wait longer for postMessage in dev mode
|
|
59
|
+
const timeout = setTimeout(() => {
|
|
60
|
+
// Only show error if we still don't have data
|
|
61
|
+
setState((prevState) => {
|
|
62
|
+
// If we already have data, don't override it
|
|
63
|
+
if (prevState.data && Object.keys(prevState.data).length > 0) {
|
|
64
|
+
return prevState; // Keep existing state
|
|
65
|
+
}
|
|
66
|
+
if (!checkForData()) {
|
|
67
|
+
return {
|
|
68
|
+
data: null,
|
|
69
|
+
loading: false,
|
|
70
|
+
error: 'No data available',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return prevState;
|
|
74
|
+
});
|
|
75
|
+
}, 5000); // Increased to 5 seconds for dev mode
|
|
76
|
+
return () => {
|
|
77
|
+
window.removeEventListener('message', handleMessage);
|
|
78
|
+
clearTimeout(timeout);
|
|
79
|
+
};
|
|
80
|
+
}, []);
|
|
81
|
+
// Don't render anything until mounted (prevents SSR issues)
|
|
82
|
+
if (!mounted) {
|
|
83
|
+
return (_jsx("div", { style: {
|
|
84
|
+
display: 'flex',
|
|
85
|
+
alignItems: 'center',
|
|
86
|
+
justifyContent: 'center',
|
|
87
|
+
minHeight: '200px',
|
|
88
|
+
background: 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)',
|
|
89
|
+
}, children: _jsx("div", { style: {
|
|
90
|
+
width: '12px',
|
|
91
|
+
height: '12px',
|
|
92
|
+
borderRadius: '50%',
|
|
93
|
+
background: '#D4AF37',
|
|
94
|
+
} }) }));
|
|
95
|
+
}
|
|
96
|
+
if (state.loading) {
|
|
97
|
+
return (_jsx("div", { style: {
|
|
98
|
+
display: 'flex',
|
|
99
|
+
alignItems: 'center',
|
|
100
|
+
justifyContent: 'center',
|
|
101
|
+
minHeight: '200px',
|
|
102
|
+
background: 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)',
|
|
103
|
+
}, children: _jsxs("div", { style: {
|
|
104
|
+
display: 'flex',
|
|
105
|
+
gap: '8px',
|
|
106
|
+
}, children: [_jsx("div", { style: {
|
|
107
|
+
width: '12px',
|
|
108
|
+
height: '12px',
|
|
109
|
+
borderRadius: '50%',
|
|
110
|
+
background: '#D4AF37',
|
|
111
|
+
animation: 'pulse 1.4s ease-in-out infinite',
|
|
112
|
+
} }), _jsx("div", { style: {
|
|
113
|
+
width: '12px',
|
|
114
|
+
height: '12px',
|
|
115
|
+
borderRadius: '50%',
|
|
116
|
+
background: '#D4AF37',
|
|
117
|
+
animation: 'pulse 1.4s ease-in-out 0.2s infinite',
|
|
118
|
+
} }), _jsx("div", { style: {
|
|
119
|
+
width: '12px',
|
|
120
|
+
height: '12px',
|
|
121
|
+
borderRadius: '50%',
|
|
122
|
+
background: '#D4AF37',
|
|
123
|
+
animation: 'pulse 1.4s ease-in-out 0.4s infinite',
|
|
124
|
+
} })] }) }));
|
|
125
|
+
}
|
|
126
|
+
if (state.error || !state.data) {
|
|
127
|
+
return (_jsxs("div", { style: {
|
|
128
|
+
padding: '40px 20px',
|
|
129
|
+
textAlign: 'center',
|
|
130
|
+
background: 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)',
|
|
131
|
+
minHeight: '200px',
|
|
132
|
+
display: 'flex',
|
|
133
|
+
flexDirection: 'column',
|
|
134
|
+
alignItems: 'center',
|
|
135
|
+
justifyContent: 'center',
|
|
136
|
+
}, children: [_jsx("div", { style: {
|
|
137
|
+
fontSize: '48px',
|
|
138
|
+
marginBottom: '16px',
|
|
139
|
+
}, children: "\u26A0\uFE0F" }), _jsx("div", { style: {
|
|
140
|
+
color: '#666',
|
|
141
|
+
fontSize: '16px',
|
|
142
|
+
fontWeight: '500',
|
|
143
|
+
}, children: state.error || 'No data available' })] }));
|
|
144
|
+
}
|
|
145
|
+
// Extra safety: validate data is an object with properties
|
|
146
|
+
// This prevents rendering if data is an empty object or invalid
|
|
147
|
+
if (typeof state.data !== 'object' || Object.keys(state.data).length === 0) {
|
|
148
|
+
return (_jsxs("div", { style: {
|
|
149
|
+
padding: '40px 20px',
|
|
150
|
+
textAlign: 'center',
|
|
151
|
+
background: 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)',
|
|
152
|
+
minHeight: '200px',
|
|
153
|
+
display: 'flex',
|
|
154
|
+
flexDirection: 'column',
|
|
155
|
+
alignItems: 'center',
|
|
156
|
+
justifyContent: 'center',
|
|
157
|
+
}, children: [_jsx("div", { style: {
|
|
158
|
+
fontSize: '48px',
|
|
159
|
+
marginBottom: '16px',
|
|
160
|
+
}, children: "\u23F3" }), _jsx("div", { style: {
|
|
161
|
+
color: '#666',
|
|
162
|
+
fontSize: '16px',
|
|
163
|
+
fontWeight: '500',
|
|
164
|
+
}, children: "Waiting for data..." })] }));
|
|
165
|
+
}
|
|
166
|
+
// Wrap component rendering in React Error Boundary to catch any rendering errors
|
|
167
|
+
// We use a class component error boundary because try-catch doesn't work with React rendering
|
|
168
|
+
return (_jsx(ErrorBoundary, { children: _jsx(WrappedComponent, { data: state.data }) }));
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Error Boundary Component to catch widget rendering errors
|
|
173
|
+
*/
|
|
174
|
+
class ErrorBoundary extends React.Component {
|
|
175
|
+
constructor(props) {
|
|
176
|
+
super(props);
|
|
177
|
+
this.state = { hasError: false, error: null };
|
|
178
|
+
}
|
|
179
|
+
static getDerivedStateFromError(error) {
|
|
180
|
+
return { hasError: true, error };
|
|
181
|
+
}
|
|
182
|
+
componentDidCatch(error, errorInfo) {
|
|
183
|
+
console.error('Widget rendering error:', error, errorInfo);
|
|
184
|
+
}
|
|
185
|
+
render() {
|
|
186
|
+
if (this.state.hasError) {
|
|
187
|
+
return (_jsxs("div", { style: {
|
|
188
|
+
padding: '40px 20px',
|
|
189
|
+
textAlign: 'center',
|
|
190
|
+
background: 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)',
|
|
191
|
+
minHeight: '200px',
|
|
192
|
+
display: 'flex',
|
|
193
|
+
flexDirection: 'column',
|
|
194
|
+
alignItems: 'center',
|
|
195
|
+
justifyContent: 'center',
|
|
196
|
+
}, children: [_jsx("div", { style: {
|
|
197
|
+
fontSize: '48px',
|
|
198
|
+
marginBottom: '16px',
|
|
199
|
+
}, children: "\u274C" }), _jsx("div", { style: {
|
|
200
|
+
color: '#666',
|
|
201
|
+
fontSize: '16px',
|
|
202
|
+
fontWeight: '500',
|
|
203
|
+
marginBottom: '8px',
|
|
204
|
+
}, children: "Widget Error" }), _jsx("div", { style: {
|
|
205
|
+
color: '#999',
|
|
206
|
+
fontSize: '12px',
|
|
207
|
+
fontFamily: 'monospace',
|
|
208
|
+
maxWidth: '400px',
|
|
209
|
+
wordBreak: 'break-word',
|
|
210
|
+
}, children: this.state.error?.message || 'Unknown error' })] }));
|
|
211
|
+
}
|
|
212
|
+
return this.props.children;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Global styles to be injected
|
|
217
|
+
*/
|
|
218
|
+
if (typeof document !== 'undefined') {
|
|
219
|
+
const style = document.createElement('style');
|
|
220
|
+
style.textContent = `
|
|
221
|
+
@keyframes pulse {
|
|
222
|
+
0%, 100% { opacity: 0.3; transform: scale(1); }
|
|
223
|
+
50% { opacity: 1; transform: scale(1.2); }
|
|
224
|
+
}
|
|
225
|
+
`;
|
|
226
|
+
document.head.appendChild(style);
|
|
227
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nitrostack/widgets",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Widget utilities for NitroStack - Build interactive UI widgets for MCP tools",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist/",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"dev": "tsc --watch",
|
|
22
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
23
|
+
"test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage",
|
|
24
|
+
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch",
|
|
25
|
+
"prepublishOnly": "npm run build && npm run test"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"nitrostack",
|
|
29
|
+
"widgets",
|
|
30
|
+
"mcp",
|
|
31
|
+
"react",
|
|
32
|
+
"openai",
|
|
33
|
+
"chatgpt",
|
|
34
|
+
"ui"
|
|
35
|
+
],
|
|
36
|
+
"author": "Abhishek Pandit <abhishekpanditofficial@gmail.com>",
|
|
37
|
+
"license": "Apache-2.0",
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
43
|
+
"@testing-library/react": "^16.3.1",
|
|
44
|
+
"@types/jest": "^29.5.14",
|
|
45
|
+
"@types/node": "^22.10.5",
|
|
46
|
+
"@types/react": "^19.2.2",
|
|
47
|
+
"jest": "^29.7.0",
|
|
48
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
49
|
+
"react": "^19.2.3",
|
|
50
|
+
"react-dom": "^19.2.3",
|
|
51
|
+
"ts-jest": "^29.2.5",
|
|
52
|
+
"typescript": "^5.7.2"
|
|
53
|
+
},
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "https://github.com/abhishekpanditofficial/nitrostack.git",
|
|
57
|
+
"directory": "typescript/packages/widgets"
|
|
58
|
+
},
|
|
59
|
+
"bugs": {
|
|
60
|
+
"url": "https://github.com/abhishekpanditofficial/nitrostack/issues"
|
|
61
|
+
},
|
|
62
|
+
"homepage": "https://nitrostack.vercel.app"
|
|
63
|
+
}
|
|
64
|
+
|