sunpeak 0.5.39 → 0.5.41
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/chatgpt/mock-openai.d.ts +2 -2
- package/dist/chatgpt/simple-sidebar.d.ts +2 -1
- package/dist/hooks/use-max-height.d.ts +1 -1
- package/dist/index.cjs +162 -144
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +162 -144
- package/dist/index.js.map +1 -1
- package/dist/providers/openai/types.d.ts +1 -1
- package/dist/providers/types.d.ts +1 -1
- package/dist/style.css +112 -35
- package/package.json +1 -1
- package/template/dist/chatgpt/albums.js +11 -11
- package/template/dist/chatgpt/carousel.js +2 -2
- package/template/dist/chatgpt/counter.js +7 -7
- package/template/node_modules/.vite/deps/_metadata.json +17 -17
- package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/template/src/components/album/albums.test.tsx +7 -2
- package/template/src/components/album/albums.tsx +1 -1
- package/template/src/components/album/fullscreen-viewer.test.tsx +12 -24
- package/template/src/components/album/fullscreen-viewer.tsx +30 -71
- package/template/src/components/carousel/carousel.tsx +1 -1
- package/template/src/components/resources/albums-resource.tsx +1 -0
- package/template/src/components/resources/counter-resource.tsx +8 -0
- package/template/src/simulations/albums-simulation.ts +5 -1
- package/template/src/simulations/carousel-simulation.ts +5 -1
- package/template/src/simulations/counter-simulation.ts +6 -1
- package/template/src/simulations/widget-config.ts +42 -0
|
@@ -7,103 +7,103 @@
|
|
|
7
7
|
"react": {
|
|
8
8
|
"src": "../../../../node_modules/.pnpm/react@19.2.0/node_modules/react/index.js",
|
|
9
9
|
"file": "react.js",
|
|
10
|
-
"fileHash": "
|
|
10
|
+
"fileHash": "264ae3aa",
|
|
11
11
|
"needsInterop": true
|
|
12
12
|
},
|
|
13
13
|
"react-dom": {
|
|
14
14
|
"src": "../../../../node_modules/.pnpm/react-dom@19.2.0_react@19.2.0/node_modules/react-dom/index.js",
|
|
15
15
|
"file": "react-dom.js",
|
|
16
|
-
"fileHash": "
|
|
16
|
+
"fileHash": "565f0670",
|
|
17
17
|
"needsInterop": true
|
|
18
18
|
},
|
|
19
19
|
"react/jsx-dev-runtime": {
|
|
20
20
|
"src": "../../../../node_modules/.pnpm/react@19.2.0/node_modules/react/jsx-dev-runtime.js",
|
|
21
21
|
"file": "react_jsx-dev-runtime.js",
|
|
22
|
-
"fileHash": "
|
|
22
|
+
"fileHash": "dc93890d",
|
|
23
23
|
"needsInterop": true
|
|
24
24
|
},
|
|
25
25
|
"react/jsx-runtime": {
|
|
26
26
|
"src": "../../../../node_modules/.pnpm/react@19.2.0/node_modules/react/jsx-runtime.js",
|
|
27
27
|
"file": "react_jsx-runtime.js",
|
|
28
|
-
"fileHash": "
|
|
28
|
+
"fileHash": "769723ed",
|
|
29
29
|
"needsInterop": true
|
|
30
30
|
},
|
|
31
31
|
"@openai/apps-sdk-ui/components/Button": {
|
|
32
32
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.0_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._60630c8dcc43ec213b3e346c9e26579b/node_modules/@openai/apps-sdk-ui/dist/es/components/Button/index.js",
|
|
33
33
|
"file": "@openai_apps-sdk-ui_components_Button.js",
|
|
34
|
-
"fileHash": "
|
|
34
|
+
"fileHash": "2aaa39fe",
|
|
35
35
|
"needsInterop": false
|
|
36
36
|
},
|
|
37
37
|
"@openai/apps-sdk-ui/components/Checkbox": {
|
|
38
38
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.0_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._60630c8dcc43ec213b3e346c9e26579b/node_modules/@openai/apps-sdk-ui/dist/es/components/Checkbox/index.js",
|
|
39
39
|
"file": "@openai_apps-sdk-ui_components_Checkbox.js",
|
|
40
|
-
"fileHash": "
|
|
40
|
+
"fileHash": "9bc36d10",
|
|
41
41
|
"needsInterop": false
|
|
42
42
|
},
|
|
43
43
|
"@openai/apps-sdk-ui/components/Icon": {
|
|
44
44
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.0_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._60630c8dcc43ec213b3e346c9e26579b/node_modules/@openai/apps-sdk-ui/dist/es/components/Icon/index.js",
|
|
45
45
|
"file": "@openai_apps-sdk-ui_components_Icon.js",
|
|
46
|
-
"fileHash": "
|
|
46
|
+
"fileHash": "0c8a7bb7",
|
|
47
47
|
"needsInterop": false
|
|
48
48
|
},
|
|
49
49
|
"@openai/apps-sdk-ui/components/Input": {
|
|
50
50
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.0_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._60630c8dcc43ec213b3e346c9e26579b/node_modules/@openai/apps-sdk-ui/dist/es/components/Input/index.js",
|
|
51
51
|
"file": "@openai_apps-sdk-ui_components_Input.js",
|
|
52
|
-
"fileHash": "
|
|
52
|
+
"fileHash": "66aa5c5a",
|
|
53
53
|
"needsInterop": false
|
|
54
54
|
},
|
|
55
55
|
"@openai/apps-sdk-ui/components/SegmentedControl": {
|
|
56
56
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.0_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._60630c8dcc43ec213b3e346c9e26579b/node_modules/@openai/apps-sdk-ui/dist/es/components/SegmentedControl/index.js",
|
|
57
57
|
"file": "@openai_apps-sdk-ui_components_SegmentedControl.js",
|
|
58
|
-
"fileHash": "
|
|
58
|
+
"fileHash": "97a2aa5f",
|
|
59
59
|
"needsInterop": false
|
|
60
60
|
},
|
|
61
61
|
"@openai/apps-sdk-ui/components/Select": {
|
|
62
62
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.0_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._60630c8dcc43ec213b3e346c9e26579b/node_modules/@openai/apps-sdk-ui/dist/es/components/Select/index.js",
|
|
63
63
|
"file": "@openai_apps-sdk-ui_components_Select.js",
|
|
64
|
-
"fileHash": "
|
|
64
|
+
"fileHash": "be97a54f",
|
|
65
65
|
"needsInterop": false
|
|
66
66
|
},
|
|
67
67
|
"@openai/apps-sdk-ui/components/Textarea": {
|
|
68
68
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.0_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._60630c8dcc43ec213b3e346c9e26579b/node_modules/@openai/apps-sdk-ui/dist/es/components/Textarea/index.js",
|
|
69
69
|
"file": "@openai_apps-sdk-ui_components_Textarea.js",
|
|
70
|
-
"fileHash": "
|
|
70
|
+
"fileHash": "cb2abaca",
|
|
71
71
|
"needsInterop": false
|
|
72
72
|
},
|
|
73
73
|
"@openai/apps-sdk-ui/theme": {
|
|
74
74
|
"src": "../../../../node_modules/.pnpm/@openai+apps-sdk-ui@0.2.0_@types+react-dom@19.2.3_@types+react@19.2.7__@types+react@19._60630c8dcc43ec213b3e346c9e26579b/node_modules/@openai/apps-sdk-ui/dist/es/lib/theme.js",
|
|
75
75
|
"file": "@openai_apps-sdk-ui_theme.js",
|
|
76
|
-
"fileHash": "
|
|
76
|
+
"fileHash": "446e068b",
|
|
77
77
|
"needsInterop": false
|
|
78
78
|
},
|
|
79
79
|
"clsx": {
|
|
80
80
|
"src": "../../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs",
|
|
81
81
|
"file": "clsx.js",
|
|
82
|
-
"fileHash": "
|
|
82
|
+
"fileHash": "07860a30",
|
|
83
83
|
"needsInterop": false
|
|
84
84
|
},
|
|
85
85
|
"embla-carousel-react": {
|
|
86
86
|
"src": "../../../../node_modules/.pnpm/embla-carousel-react@8.6.0_react@19.2.0/node_modules/embla-carousel-react/esm/embla-carousel-react.esm.js",
|
|
87
87
|
"file": "embla-carousel-react.js",
|
|
88
|
-
"fileHash": "
|
|
88
|
+
"fileHash": "361056e7",
|
|
89
89
|
"needsInterop": false
|
|
90
90
|
},
|
|
91
91
|
"embla-carousel-wheel-gestures": {
|
|
92
92
|
"src": "../../../../node_modules/.pnpm/embla-carousel-wheel-gestures@8.1.0_embla-carousel@8.6.0/node_modules/embla-carousel-wheel-gestures/dist/embla-carousel-wheel-gestures.esm.js",
|
|
93
93
|
"file": "embla-carousel-wheel-gestures.js",
|
|
94
|
-
"fileHash": "
|
|
94
|
+
"fileHash": "a42e64fa",
|
|
95
95
|
"needsInterop": false
|
|
96
96
|
},
|
|
97
97
|
"react-dom/client": {
|
|
98
98
|
"src": "../../../../node_modules/.pnpm/react-dom@19.2.0_react@19.2.0/node_modules/react-dom/client.js",
|
|
99
99
|
"file": "react-dom_client.js",
|
|
100
|
-
"fileHash": "
|
|
100
|
+
"fileHash": "39ff4ecc",
|
|
101
101
|
"needsInterop": true
|
|
102
102
|
},
|
|
103
103
|
"tailwind-merge": {
|
|
104
104
|
"src": "../../../../node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.mjs",
|
|
105
105
|
"file": "tailwind-merge.js",
|
|
106
|
-
"fileHash": "
|
|
106
|
+
"fileHash": "01cd3e08",
|
|
107
107
|
"needsInterop": false
|
|
108
108
|
}
|
|
109
109
|
},
|
package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"4.0.13","results":[[":src/components/album/
|
|
1
|
+
{"version":"4.0.13","results":[[":src/components/album/albums.test.tsx",{"duration":370.11665100000005,"failed":false}],[":src/components/resources/counter-resource.test.tsx",{"duration":311.2705619999997,"failed":false}],[":src/components/resources/carousel-resource.test.tsx",{"duration":257.46737600000006,"failed":false}],[":src/components/album/fullscreen-viewer.test.tsx",{"duration":232.20117000000027,"failed":false}],[":src/components/carousel/carousel.test.tsx",{"duration":86.45302899999979,"failed":false}],[":src/components/resources/albums-resource.test.tsx",{"duration":273.0157959999999,"failed":false}],[":src/components/album/film-strip.test.tsx",{"duration":458.94508499999984,"failed":false}],[":src/components/album/album-card.test.tsx",{"duration":290.17094999999995,"failed":false}],[":src/components/card/card.test.tsx",{"duration":54.771146000000044,"failed":false}]]}
|
|
@@ -91,8 +91,13 @@ describe('Albums', () => {
|
|
|
91
91
|
const firstAlbum = screen.getByText('Summer Vacation').closest('button')!;
|
|
92
92
|
fireEvent.click(firstAlbum);
|
|
93
93
|
|
|
94
|
-
// Should update widget state with selected album ID
|
|
95
|
-
expect(mockSetWidgetState).
|
|
94
|
+
// Should update widget state with selected album ID using function updater
|
|
95
|
+
expect(mockSetWidgetState).toHaveBeenCalledTimes(1);
|
|
96
|
+
const updateFn = mockSetWidgetState.mock.calls[0][0];
|
|
97
|
+
expect(typeof updateFn).toBe('function');
|
|
98
|
+
// Test the updater function
|
|
99
|
+
const result = updateFn({ currentIndex: 0 });
|
|
100
|
+
expect(result).toEqual({ currentIndex: 0, selectedAlbumId: 'album-1' });
|
|
96
101
|
|
|
97
102
|
// Should request fullscreen mode
|
|
98
103
|
expect(mockRequestDisplayMode).toHaveBeenCalledWith({ mode: 'fullscreen' });
|
|
@@ -48,7 +48,7 @@ export const Albums = React.forwardRef<HTMLDivElement, AlbumsProps>(({ className
|
|
|
48
48
|
|
|
49
49
|
const handleSelectAlbum = React.useCallback(
|
|
50
50
|
(album: Album) => {
|
|
51
|
-
setWidgetState({ selectedAlbumId: album.id });
|
|
51
|
+
setWidgetState((prev) => ({ ...prev, selectedAlbumId: album.id }));
|
|
52
52
|
api?.requestDisplayMode?.({ mode: 'fullscreen' });
|
|
53
53
|
},
|
|
54
54
|
[setWidgetState, api]
|
|
@@ -4,11 +4,9 @@ import { FullscreenViewer } from './fullscreen-viewer';
|
|
|
4
4
|
import type { Album } from './albums';
|
|
5
5
|
|
|
6
6
|
// Mock sunpeak hooks
|
|
7
|
-
let mockMaxHeight = 800;
|
|
8
7
|
let mockSafeArea = { insets: { top: 0, bottom: 0, left: 0, right: 0 } };
|
|
9
8
|
|
|
10
9
|
vi.mock('sunpeak', () => ({
|
|
11
|
-
useMaxHeight: () => mockMaxHeight,
|
|
12
10
|
useSafeArea: () => mockSafeArea,
|
|
13
11
|
}));
|
|
14
12
|
|
|
@@ -27,8 +25,8 @@ describe('FullscreenViewer', () => {
|
|
|
27
25
|
it('resets to first photo when album changes', () => {
|
|
28
26
|
const { rerender, container } = render(<FullscreenViewer album={mockAlbum} />);
|
|
29
27
|
|
|
30
|
-
// Get the main photo area
|
|
31
|
-
const mainPhotoArea = container.querySelector('.flex-1
|
|
28
|
+
// Get the main photo area
|
|
29
|
+
const mainPhotoArea = container.querySelector('.flex-1');
|
|
32
30
|
let mainPhoto = mainPhotoArea?.querySelector('img');
|
|
33
31
|
expect(mainPhoto).toHaveAttribute('alt', 'First Photo');
|
|
34
32
|
expect(mainPhoto).toHaveAttribute('src', 'https://example.com/1.jpg');
|
|
@@ -56,8 +54,8 @@ describe('FullscreenViewer', () => {
|
|
|
56
54
|
it('displays correct photo based on selected index from FilmStrip', () => {
|
|
57
55
|
const { container } = render(<FullscreenViewer album={mockAlbum} />);
|
|
58
56
|
|
|
59
|
-
// Get the main photo
|
|
60
|
-
const mainPhotoArea = container.querySelector('.flex-1
|
|
57
|
+
// Get the main photo
|
|
58
|
+
const mainPhotoArea = container.querySelector('.flex-1');
|
|
61
59
|
const firstPhoto = mainPhotoArea?.querySelector('img');
|
|
62
60
|
|
|
63
61
|
expect(firstPhoto).toHaveAttribute('alt', 'First Photo');
|
|
@@ -79,28 +77,18 @@ describe('FullscreenViewer', () => {
|
|
|
79
77
|
expect(images.length).toBe(0);
|
|
80
78
|
});
|
|
81
79
|
|
|
82
|
-
it('respects safe area insets
|
|
80
|
+
it('respects safe area insets', () => {
|
|
83
81
|
mockSafeArea = { insets: { top: 20, bottom: 30, left: 10, right: 15 } };
|
|
84
82
|
|
|
85
83
|
const { container } = render(<FullscreenViewer album={mockAlbum} />);
|
|
86
84
|
|
|
87
|
-
//
|
|
88
|
-
const
|
|
89
|
-
expect(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
it('respects maxHeight constraint', () => {
|
|
96
|
-
mockMaxHeight = 600;
|
|
97
|
-
|
|
98
|
-
const { container } = render(<FullscreenViewer album={mockAlbum} />);
|
|
99
|
-
|
|
100
|
-
const mainDiv = container.firstChild as HTMLElement;
|
|
101
|
-
expect(mainDiv).toHaveStyle({
|
|
102
|
-
maxHeight: '600px',
|
|
103
|
-
height: '600px',
|
|
85
|
+
// Check root div has safe area padding
|
|
86
|
+
const rootDiv = container.firstChild as HTMLElement;
|
|
87
|
+
expect(rootDiv).toHaveStyle({
|
|
88
|
+
paddingTop: '20px',
|
|
89
|
+
paddingBottom: '30px',
|
|
90
|
+
paddingLeft: '10px',
|
|
91
|
+
paddingRight: '15px',
|
|
104
92
|
});
|
|
105
93
|
});
|
|
106
94
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { useSafeArea } from 'sunpeak';
|
|
3
3
|
import { cn } from '../../lib/index';
|
|
4
4
|
import { FilmStrip } from './film-strip';
|
|
5
5
|
import type { Album } from './albums';
|
|
@@ -11,7 +11,6 @@ export type FullscreenViewerProps = {
|
|
|
11
11
|
|
|
12
12
|
export const FullscreenViewer = React.forwardRef<HTMLDivElement, FullscreenViewerProps>(
|
|
13
13
|
({ album, className }, ref) => {
|
|
14
|
-
const maxHeight = useMaxHeight();
|
|
15
14
|
const safeArea = useSafeArea();
|
|
16
15
|
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
17
16
|
const [width, setWidth] = React.useState(0);
|
|
@@ -49,80 +48,40 @@ export const FullscreenViewer = React.forwardRef<HTMLDivElement, FullscreenViewe
|
|
|
49
48
|
return (
|
|
50
49
|
<div
|
|
51
50
|
ref={containerRef}
|
|
52
|
-
className={cn('
|
|
51
|
+
className={cn('flex w-full bg-surface', isMobile ? 'flex-col' : 'flex-row', className)}
|
|
53
52
|
style={{
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
['--safe-left' as string]: `${safeArea?.insets.left ?? 0}px`,
|
|
59
|
-
['--safe-right' as string]: `${safeArea?.insets.right ?? 0}px`,
|
|
53
|
+
paddingTop: `${safeArea?.insets.top ?? 0}px`,
|
|
54
|
+
paddingBottom: `${safeArea?.insets.bottom ?? 0}px`,
|
|
55
|
+
paddingLeft: `${safeArea?.insets.left ?? 0}px`,
|
|
56
|
+
paddingRight: `${safeArea?.insets.right ?? 0}px`,
|
|
60
57
|
}}
|
|
61
58
|
>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
className="z-10 border-b border-subtle bg-surface/95 backdrop-blur-sm"
|
|
72
|
-
style={{
|
|
73
|
-
paddingTop: `calc(0.75rem + var(--safe-top))`,
|
|
74
|
-
paddingBottom: '0.75rem',
|
|
75
|
-
paddingLeft: `calc(1rem + var(--safe-left))`,
|
|
76
|
-
paddingRight: `calc(1rem + var(--safe-right))`,
|
|
77
|
-
}}
|
|
78
|
-
>
|
|
79
|
-
<h2 className="text-base font-semibold text-primary">{album.title}</h2>
|
|
80
|
-
<p className="text-sm text-secondary">
|
|
81
|
-
{selectedIndex + 1} / {album.photos.length}
|
|
82
|
-
</p>
|
|
83
|
-
</div>
|
|
84
|
-
)}
|
|
85
|
-
|
|
86
|
-
{/* Film strip - desktop only */}
|
|
87
|
-
{!isMobile && (
|
|
88
|
-
<div
|
|
89
|
-
className="absolute pointer-events-none z-10 left-0 top-0 bottom-0 w-40"
|
|
90
|
-
style={{
|
|
91
|
-
paddingLeft: `var(--safe-left)`,
|
|
92
|
-
}}
|
|
93
|
-
>
|
|
94
|
-
<FilmStrip album={album} selectedIndex={selectedIndex} onSelect={setSelectedIndex} />
|
|
95
|
-
</div>
|
|
96
|
-
)}
|
|
59
|
+
{/* Album header - mobile only */}
|
|
60
|
+
{isMobile && (
|
|
61
|
+
<div className="border-b border-subtle bg-surface/95 backdrop-blur-sm px-4 py-3">
|
|
62
|
+
<h2 className="text-base font-semibold text-primary">{album.title}</h2>
|
|
63
|
+
<p className="text-sm text-secondary">
|
|
64
|
+
{selectedIndex + 1} / {album.photos.length}
|
|
65
|
+
</p>
|
|
66
|
+
</div>
|
|
67
|
+
)}
|
|
97
68
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
paddingTop: isMobile
|
|
103
|
-
? `calc(1rem + var(--safe-top))`
|
|
104
|
-
: `calc(2.5rem + var(--safe-top))`,
|
|
105
|
-
paddingBottom: isMobile
|
|
106
|
-
? `calc(1rem + var(--safe-bottom))`
|
|
107
|
-
: `calc(2.5rem + var(--safe-bottom))`,
|
|
108
|
-
paddingLeft: isMobile
|
|
109
|
-
? `calc(1rem + var(--safe-left))`
|
|
110
|
-
: `calc(10rem + var(--safe-left))`,
|
|
111
|
-
paddingRight: isMobile
|
|
112
|
-
? `calc(1rem + var(--safe-right))`
|
|
113
|
-
: `calc(10rem + var(--safe-right))`,
|
|
114
|
-
}}
|
|
115
|
-
>
|
|
116
|
-
<div className="relative w-full h-full">
|
|
117
|
-
{selectedPhoto ? (
|
|
118
|
-
<img
|
|
119
|
-
src={selectedPhoto.url}
|
|
120
|
-
alt={selectedPhoto.title || album.title}
|
|
121
|
-
className="absolute inset-0 m-auto rounded-3xl shadow-sm border border-primary/10 max-w-full max-h-full object-contain"
|
|
122
|
-
/>
|
|
123
|
-
) : null}
|
|
124
|
-
</div>
|
|
69
|
+
{/* Film strip - desktop only */}
|
|
70
|
+
{!isMobile && (
|
|
71
|
+
<div className="w-40 flex-shrink-0">
|
|
72
|
+
<FilmStrip album={album} selectedIndex={selectedIndex} onSelect={setSelectedIndex} />
|
|
125
73
|
</div>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
{/* Main photo */}
|
|
77
|
+
<div className="flex-1 flex items-center justify-center p-4 md:p-10">
|
|
78
|
+
{selectedPhoto ? (
|
|
79
|
+
<img
|
|
80
|
+
src={selectedPhoto.url}
|
|
81
|
+
alt={selectedPhoto.title || album.title}
|
|
82
|
+
className="rounded-3xl shadow-sm border border-primary/10 max-w-full max-h-full object-contain"
|
|
83
|
+
/>
|
|
84
|
+
) : null}
|
|
126
85
|
</div>
|
|
127
86
|
</div>
|
|
128
87
|
);
|
|
@@ -57,7 +57,7 @@ export const Carousel = React.forwardRef<HTMLDivElement, CarouselProps>(
|
|
|
57
57
|
|
|
58
58
|
const currentIndex = emblaApi.selectedScrollSnap();
|
|
59
59
|
if (widgetState?.currentIndex !== currentIndex) {
|
|
60
|
-
setWidgetState({ currentIndex });
|
|
60
|
+
setWidgetState((prev) => ({ ...prev, currentIndex }));
|
|
61
61
|
}
|
|
62
62
|
}, [emblaApi, widgetState?.currentIndex, setWidgetState]);
|
|
63
63
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useWidgetState, useSafeArea, useMaxHeight, useUserAgent } from 'sunpeak';
|
|
2
2
|
import { Button } from '@openai/apps-sdk-ui/components/Button';
|
|
3
|
+
import { useEffect } from 'react';
|
|
3
4
|
|
|
4
5
|
interface CounterState extends Record<string, unknown> {
|
|
5
6
|
count?: number;
|
|
@@ -22,6 +23,13 @@ export function CounterResource() {
|
|
|
22
23
|
const count = widgetState?.count ?? 0;
|
|
23
24
|
const hasTouch = userAgent?.capabilities.touch ?? false;
|
|
24
25
|
|
|
26
|
+
// Initialize count to 0 if not set
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (widgetState?.count === undefined) {
|
|
29
|
+
setWidgetState({ count: 0 });
|
|
30
|
+
}
|
|
31
|
+
}, [widgetState?.count, setWidgetState]);
|
|
32
|
+
|
|
25
33
|
const increment = () => {
|
|
26
34
|
setWidgetState({ count: count + 1 });
|
|
27
35
|
};
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* This file contains only metadata and doesn't import React components or CSS.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { defaultWidgetMeta } from './widget-config';
|
|
7
|
+
|
|
6
8
|
const albumsData = {
|
|
7
9
|
albums: [
|
|
8
10
|
{
|
|
@@ -144,7 +146,9 @@ export const albumsSimulation = {
|
|
|
144
146
|
title: 'Albums',
|
|
145
147
|
description: 'Show photo albums widget markup',
|
|
146
148
|
mimeType: 'text/html+skybridge',
|
|
147
|
-
_meta: {
|
|
149
|
+
_meta: {
|
|
150
|
+
...defaultWidgetMeta,
|
|
151
|
+
},
|
|
148
152
|
},
|
|
149
153
|
|
|
150
154
|
// MCP CallTool protocol - data for CallTool response
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* This file contains only metadata and doesn't import React components or CSS.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { defaultWidgetMeta } from './widget-config';
|
|
7
|
+
|
|
6
8
|
const placesData = {
|
|
7
9
|
places: [
|
|
8
10
|
{
|
|
@@ -81,7 +83,9 @@ export const carouselSimulation = {
|
|
|
81
83
|
title: 'Carousel',
|
|
82
84
|
description: 'Show popular places to visit widget markup',
|
|
83
85
|
mimeType: 'text/html+skybridge',
|
|
84
|
-
_meta: {
|
|
86
|
+
_meta: {
|
|
87
|
+
...defaultWidgetMeta,
|
|
88
|
+
},
|
|
85
89
|
},
|
|
86
90
|
|
|
87
91
|
// MCP CallTool protocol - data for CallTool response
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* Server-safe configuration for the counter simulation.
|
|
3
3
|
* This file contains only metadata and doesn't import React components or CSS.
|
|
4
4
|
*/
|
|
5
|
+
|
|
6
|
+
import { defaultWidgetMeta } from './widget-config';
|
|
7
|
+
|
|
5
8
|
export const counterSimulation = {
|
|
6
9
|
userMessage: 'Help me count something',
|
|
7
10
|
|
|
@@ -30,7 +33,9 @@ export const counterSimulation = {
|
|
|
30
33
|
title: 'Counter',
|
|
31
34
|
description: 'Show a simple counter tool widget markup',
|
|
32
35
|
mimeType: 'text/html+skybridge',
|
|
33
|
-
_meta: {
|
|
36
|
+
_meta: {
|
|
37
|
+
...defaultWidgetMeta,
|
|
38
|
+
},
|
|
34
39
|
},
|
|
35
40
|
|
|
36
41
|
// MCP CallTool protocol - data for CallTool response
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default widget configuration for all simulations.
|
|
3
|
+
* Individual simulations can override these values as needed.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface WidgetCSP {
|
|
7
|
+
connect_domains: string[];
|
|
8
|
+
resource_domains: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface WidgetMeta {
|
|
12
|
+
'openai/widgetDomain': string;
|
|
13
|
+
'openai/widgetCSP': WidgetCSP;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Default widget metadata that can be spread into resource._meta
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // Use default configuration
|
|
21
|
+
* resource: {
|
|
22
|
+
* _meta: {
|
|
23
|
+
* ...defaultWidgetMeta,
|
|
24
|
+
* }
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Override specific values
|
|
29
|
+
* resource: {
|
|
30
|
+
* _meta: {
|
|
31
|
+
* ...defaultWidgetMeta,
|
|
32
|
+
* 'openai/widgetDomain': 'https://custom.domain.com',
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
*/
|
|
36
|
+
export const defaultWidgetMeta: WidgetMeta = {
|
|
37
|
+
'openai/widgetDomain': 'https://sunpeak.ai', // YOUR DOMAIN HERE.
|
|
38
|
+
'openai/widgetCSP': {
|
|
39
|
+
connect_domains: ['https://sunpeak.ai'], // YOUR API HERE.
|
|
40
|
+
resource_domains: ['https://*.oaistatic.com', 'https://images.unsplash.com'], // YOUR CDN HERE (if any).
|
|
41
|
+
},
|
|
42
|
+
};
|