@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.
Files changed (47) hide show
  1. package/README.md +136 -0
  2. package/dist/hooks/index.d.ts +7 -0
  3. package/dist/hooks/index.d.ts.map +1 -0
  4. package/dist/hooks/index.js +6 -0
  5. package/dist/hooks/use-display-mode.d.ts +11 -0
  6. package/dist/hooks/use-display-mode.d.ts.map +1 -0
  7. package/dist/hooks/use-display-mode.js +12 -0
  8. package/dist/hooks/use-max-height.d.ts +10 -0
  9. package/dist/hooks/use-max-height.d.ts.map +1 -0
  10. package/dist/hooks/use-max-height.js +12 -0
  11. package/dist/hooks/use-openai-global.d.ts +12 -0
  12. package/dist/hooks/use-openai-global.d.ts.map +1 -0
  13. package/dist/hooks/use-openai-global.js +31 -0
  14. package/dist/hooks/use-theme.d.ts +10 -0
  15. package/dist/hooks/use-theme.d.ts.map +1 -0
  16. package/dist/hooks/use-theme.js +11 -0
  17. package/dist/hooks/use-widget-state.d.ts +18 -0
  18. package/dist/hooks/use-widget-state.d.ts.map +1 -0
  19. package/dist/hooks/use-widget-state.js +26 -0
  20. package/dist/hooks/useWidgetSDK.d.ts +49 -0
  21. package/dist/hooks/useWidgetSDK.d.ts.map +1 -0
  22. package/dist/hooks/useWidgetSDK.js +69 -0
  23. package/dist/index.d.ts +15 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +18 -0
  26. package/dist/metadata.d.ts +53 -0
  27. package/dist/metadata.d.ts.map +1 -0
  28. package/dist/metadata.js +28 -0
  29. package/dist/runtime/WidgetLayout.d.ts +32 -0
  30. package/dist/runtime/WidgetLayout.d.ts.map +1 -0
  31. package/dist/runtime/WidgetLayout.js +143 -0
  32. package/dist/runtime/widget-polyfill.d.ts +2 -0
  33. package/dist/runtime/widget-polyfill.d.ts.map +1 -0
  34. package/dist/runtime/widget-polyfill.js +27 -0
  35. package/dist/sdk.d.ts +117 -0
  36. package/dist/sdk.d.ts.map +1 -0
  37. package/dist/sdk.js +232 -0
  38. package/dist/types.d.ts +89 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +7 -0
  41. package/dist/utils/media-queries.d.ts +34 -0
  42. package/dist/utils/media-queries.d.ts.map +1 -0
  43. package/dist/utils/media-queries.js +41 -0
  44. package/dist/withToolData.d.ts +19 -0
  45. package/dist/withToolData.d.ts.map +1 -0
  46. package/dist/withToolData.js +227 -0
  47. 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
+