sunpeak 0.5.8 → 0.5.10
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 +13 -11
- package/bin/sunpeak.js +3 -3
- package/dist/chatgpt/mock-openai.d.ts +7 -0
- package/dist/chatgpt/simple-sidebar.d.ts +38 -0
- package/dist/chatgpt/theme-provider.d.ts +2 -2
- package/dist/index.cjs +7733 -199
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +7734 -201
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.cjs +80 -106
- package/dist/mcp/index.cjs.map +1 -1
- package/dist/mcp/index.js +80 -106
- package/dist/mcp/index.js.map +1 -1
- package/dist/style.css +2890 -315
- package/package.json +6 -5
- package/template/README.md +1 -0
- package/template/dev/main.tsx +6 -10
- package/template/package.json +5 -4
- package/template/scripts/build-all.mjs +19 -10
- package/template/scripts/validate.mjs +8 -2
- package/template/src/components/album/album-card.test.tsx +62 -0
- package/template/src/components/album/album-card.tsx +14 -16
- package/template/src/components/album/albums.test.tsx +88 -0
- package/template/src/components/album/albums.tsx +50 -64
- package/template/src/components/album/film-strip.test.tsx +64 -0
- package/template/src/components/album/film-strip.tsx +16 -16
- package/template/src/components/album/fullscreen-viewer.test.tsx +77 -0
- package/template/src/components/album/fullscreen-viewer.tsx +45 -50
- package/template/src/components/card/card.test.tsx +1 -4
- package/template/src/components/card/card.tsx +38 -46
- package/template/src/components/carousel/carousel.tsx +57 -67
- package/template/src/components/resources/{AlbumsResource.tsx → albums-resource.tsx} +5 -5
- package/template/src/components/resources/{CarouselResource.tsx → carousel-resource.tsx} +18 -18
- package/template/src/components/resources/{CounterResource.tsx → counter-resource.tsx} +11 -31
- package/template/src/components/resources/index.ts +3 -3
- package/template/src/simulations/albums-simulation.ts +71 -71
- package/template/src/simulations/carousel-simulation.ts +34 -34
- package/template/src/simulations/counter-simulation.ts +2 -2
- package/template/vite.config.build.ts +2 -2
- package/template/vite.config.ts +1 -1
- package/template/vitest.config.ts +1 -1
- package/dist/runtime/index.d.ts +0 -7
- /package/dist/{runtime → providers}/provider-detection.d.ts +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import { FullscreenViewer } from './fullscreen-viewer';
|
|
4
|
+
import type { Album } from './albums';
|
|
5
|
+
|
|
6
|
+
// Mock sunpeak hooks
|
|
7
|
+
vi.mock('sunpeak', () => ({
|
|
8
|
+
useMaxHeight: () => 800,
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
describe('FullscreenViewer', () => {
|
|
12
|
+
const mockAlbum: Album = {
|
|
13
|
+
id: 'album-1',
|
|
14
|
+
title: 'Test Album',
|
|
15
|
+
cover: 'https://example.com/cover.jpg',
|
|
16
|
+
photos: [
|
|
17
|
+
{ id: '1', title: 'First Photo', url: 'https://example.com/1.jpg' },
|
|
18
|
+
{ id: '2', title: 'Second Photo', url: 'https://example.com/2.jpg' },
|
|
19
|
+
{ id: '3', title: 'Third Photo', url: 'https://example.com/3.jpg' },
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
it('resets to first photo when album changes', () => {
|
|
24
|
+
const { rerender, container } = render(<FullscreenViewer album={mockAlbum} />);
|
|
25
|
+
|
|
26
|
+
// Get the main photo area (not the film strip)
|
|
27
|
+
const mainPhotoArea = container.querySelector('.flex-1.min-w-0');
|
|
28
|
+
let mainPhoto = mainPhotoArea?.querySelector('img');
|
|
29
|
+
expect(mainPhoto).toHaveAttribute('alt', 'First Photo');
|
|
30
|
+
expect(mainPhoto).toHaveAttribute('src', 'https://example.com/1.jpg');
|
|
31
|
+
|
|
32
|
+
// Create a different album
|
|
33
|
+
const differentAlbum: Album = {
|
|
34
|
+
id: 'album-2',
|
|
35
|
+
title: 'Different Album',
|
|
36
|
+
cover: 'https://example.com/cover2.jpg',
|
|
37
|
+
photos: [
|
|
38
|
+
{ id: '4', title: 'New First Photo', url: 'https://example.com/4.jpg' },
|
|
39
|
+
{ id: '5', title: 'New Second Photo', url: 'https://example.com/5.jpg' },
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Rerender with different album
|
|
44
|
+
rerender(<FullscreenViewer album={differentAlbum} />);
|
|
45
|
+
|
|
46
|
+
// Should show the first photo of the new album
|
|
47
|
+
mainPhoto = mainPhotoArea?.querySelector('img');
|
|
48
|
+
expect(mainPhoto).toHaveAttribute('alt', 'New First Photo');
|
|
49
|
+
expect(mainPhoto).toHaveAttribute('src', 'https://example.com/4.jpg');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('displays correct photo based on selected index from FilmStrip', () => {
|
|
53
|
+
const { container } = render(<FullscreenViewer album={mockAlbum} />);
|
|
54
|
+
|
|
55
|
+
// Get the main photo (not from film strip)
|
|
56
|
+
const mainPhotoArea = container.querySelector('.flex-1.min-w-0');
|
|
57
|
+
const firstPhoto = mainPhotoArea?.querySelector('img');
|
|
58
|
+
|
|
59
|
+
expect(firstPhoto).toHaveAttribute('alt', 'First Photo');
|
|
60
|
+
expect(firstPhoto).toHaveAttribute('src', 'https://example.com/1.jpg');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('handles empty photos array gracefully', () => {
|
|
64
|
+
const emptyAlbum: Album = {
|
|
65
|
+
id: 'empty-album',
|
|
66
|
+
title: 'Empty Album',
|
|
67
|
+
cover: 'https://example.com/cover.jpg',
|
|
68
|
+
photos: [],
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const { container } = render(<FullscreenViewer album={emptyAlbum} />);
|
|
72
|
+
|
|
73
|
+
// Should not render any img element in the main photo area
|
|
74
|
+
const images = container.querySelectorAll('img');
|
|
75
|
+
expect(images.length).toBe(0);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -1,60 +1,55 @@
|
|
|
1
|
-
import * as React from
|
|
2
|
-
import { useMaxHeight } from
|
|
3
|
-
import { cn } from
|
|
4
|
-
import { FilmStrip } from
|
|
5
|
-
import type { Album } from
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useMaxHeight } from 'sunpeak';
|
|
3
|
+
import { cn } from '../../lib/index';
|
|
4
|
+
import { FilmStrip } from './film-strip';
|
|
5
|
+
import type { Album } from './albums';
|
|
6
6
|
|
|
7
7
|
export type FullscreenViewerProps = {
|
|
8
|
-
album: Album
|
|
9
|
-
className?: string
|
|
10
|
-
}
|
|
8
|
+
album: Album;
|
|
9
|
+
className?: string;
|
|
10
|
+
};
|
|
11
11
|
|
|
12
|
-
export const FullscreenViewer = React.forwardRef<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const maxHeight = useMaxHeight()
|
|
17
|
-
const [selectedIndex, setSelectedIndex] = React.useState(0)
|
|
12
|
+
export const FullscreenViewer = React.forwardRef<HTMLDivElement, FullscreenViewerProps>(
|
|
13
|
+
({ album, className }, ref) => {
|
|
14
|
+
const maxHeight = useMaxHeight();
|
|
15
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
setSelectedIndex(0);
|
|
19
|
+
}, [album?.id]);
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
const selectedPhoto = album?.photos?.[selectedIndex];
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
selectedIndex={selectedIndex}
|
|
40
|
-
onSelect={setSelectedIndex}
|
|
41
|
-
/>
|
|
42
|
-
</div>
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
ref={ref}
|
|
26
|
+
className={cn('relative w-full h-full bg-surface', className)}
|
|
27
|
+
style={{
|
|
28
|
+
maxHeight: maxHeight ?? undefined,
|
|
29
|
+
height: maxHeight ?? undefined,
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
<div className="absolute inset-0 flex flex-row overflow-hidden">
|
|
33
|
+
{/* Film strip */}
|
|
34
|
+
<div className="hidden md:block absolute pointer-events-none z-10 left-0 top-0 bottom-0 w-40">
|
|
35
|
+
<FilmStrip album={album} selectedIndex={selectedIndex} onSelect={setSelectedIndex} />
|
|
36
|
+
</div>
|
|
43
37
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
38
|
+
{/* Main photo */}
|
|
39
|
+
<div className="flex-1 min-w-0 px-40 py-10 relative flex items-center justify-center">
|
|
40
|
+
<div className="relative w-full h-full">
|
|
41
|
+
{selectedPhoto ? (
|
|
42
|
+
<img
|
|
43
|
+
src={selectedPhoto.url}
|
|
44
|
+
alt={selectedPhoto.title || album.title}
|
|
45
|
+
className="absolute inset-0 m-auto rounded-3xl shadow-sm border border-primary/10 max-w-full max-h-full object-contain"
|
|
46
|
+
/>
|
|
47
|
+
) : null}
|
|
48
|
+
</div>
|
|
54
49
|
</div>
|
|
55
50
|
</div>
|
|
56
51
|
</div>
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
FullscreenViewer.displayName =
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
FullscreenViewer.displayName = 'FullscreenViewer';
|
|
@@ -33,10 +33,7 @@ describe('Card', () => {
|
|
|
33
33
|
const button1OnClick = vi.fn();
|
|
34
34
|
|
|
35
35
|
render(
|
|
36
|
-
<Card
|
|
37
|
-
onClick={cardOnClick}
|
|
38
|
-
button1={{ onClick: button1OnClick, children: 'Click Me' }}
|
|
39
|
-
>
|
|
36
|
+
<Card onClick={cardOnClick} button1={{ onClick: button1OnClick, children: 'Click Me' }}>
|
|
40
37
|
Content
|
|
41
38
|
</Card>
|
|
42
39
|
);
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import * as React from
|
|
2
|
-
import { Button } from
|
|
3
|
-
import { cn } from
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Button } from '@openai/apps-sdk-ui/components/Button';
|
|
3
|
+
import { cn } from '../../lib/index';
|
|
4
4
|
|
|
5
5
|
export interface CardButtonProps {
|
|
6
|
-
isPrimary?: boolean
|
|
7
|
-
onClick: () => void
|
|
8
|
-
children: React.ReactNode
|
|
6
|
+
isPrimary?: boolean;
|
|
7
|
+
onClick: () => void;
|
|
8
|
+
children: React.ReactNode;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
12
|
-
children?: React.ReactNode
|
|
13
|
-
image?: string
|
|
14
|
-
imageAlt?: string
|
|
15
|
-
imageMaxWidth?: number
|
|
16
|
-
imageMaxHeight?: number
|
|
17
|
-
header?: React.ReactNode
|
|
18
|
-
metadata?: React.ReactNode
|
|
19
|
-
button1?: CardButtonProps
|
|
20
|
-
button2?: CardButtonProps
|
|
21
|
-
variant?:
|
|
12
|
+
children?: React.ReactNode;
|
|
13
|
+
image?: string;
|
|
14
|
+
imageAlt?: string;
|
|
15
|
+
imageMaxWidth?: number;
|
|
16
|
+
imageMaxHeight?: number;
|
|
17
|
+
header?: React.ReactNode;
|
|
18
|
+
metadata?: React.ReactNode;
|
|
19
|
+
button1?: CardButtonProps;
|
|
20
|
+
button2?: CardButtonProps;
|
|
21
|
+
variant?: 'default' | 'bordered' | 'elevated';
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export const Card = React.forwardRef<HTMLDivElement, CardProps>(
|
|
@@ -33,7 +33,7 @@ export const Card = React.forwardRef<HTMLDivElement, CardProps>(
|
|
|
33
33
|
metadata,
|
|
34
34
|
button1,
|
|
35
35
|
button2,
|
|
36
|
-
variant =
|
|
36
|
+
variant = 'default',
|
|
37
37
|
className,
|
|
38
38
|
onClick,
|
|
39
39
|
...props
|
|
@@ -41,41 +41,41 @@ export const Card = React.forwardRef<HTMLDivElement, CardProps>(
|
|
|
41
41
|
ref
|
|
42
42
|
) => {
|
|
43
43
|
const variantClasses = {
|
|
44
|
-
default:
|
|
45
|
-
bordered:
|
|
46
|
-
elevated:
|
|
47
|
-
}
|
|
44
|
+
default: 'border border-subtle bg-surface',
|
|
45
|
+
bordered: 'border-2 border-default bg-surface',
|
|
46
|
+
elevated: 'border border-subtle bg-surface shadow-lg',
|
|
47
|
+
};
|
|
48
48
|
|
|
49
49
|
const handleCardClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
50
|
-
onClick?.(e)
|
|
51
|
-
}
|
|
50
|
+
onClick?.(e);
|
|
51
|
+
};
|
|
52
52
|
|
|
53
53
|
const renderButton = (buttonProps: CardButtonProps) => {
|
|
54
|
-
const { isPrimary = false, onClick: buttonOnClick, children } = buttonProps
|
|
54
|
+
const { isPrimary = false, onClick: buttonOnClick, children } = buttonProps;
|
|
55
55
|
|
|
56
56
|
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
57
|
-
e.stopPropagation()
|
|
58
|
-
buttonOnClick()
|
|
59
|
-
}
|
|
57
|
+
e.stopPropagation();
|
|
58
|
+
buttonOnClick();
|
|
59
|
+
};
|
|
60
60
|
|
|
61
61
|
return (
|
|
62
62
|
<Button
|
|
63
|
-
color={isPrimary ?
|
|
64
|
-
variant={isPrimary ?
|
|
63
|
+
color={isPrimary ? 'primary' : 'secondary'}
|
|
64
|
+
variant={isPrimary ? 'solid' : 'soft'}
|
|
65
65
|
onClick={handleClick}
|
|
66
66
|
>
|
|
67
67
|
{children}
|
|
68
68
|
</Button>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
71
|
|
|
72
|
-
const hasButtons = button1 || button2
|
|
72
|
+
const hasButtons = button1 || button2;
|
|
73
73
|
|
|
74
74
|
return (
|
|
75
75
|
<div
|
|
76
76
|
ref={ref}
|
|
77
77
|
className={cn(
|
|
78
|
-
|
|
78
|
+
'overflow-hidden rounded-2xl cursor-pointer select-none',
|
|
79
79
|
variantClasses[variant],
|
|
80
80
|
className
|
|
81
81
|
)}
|
|
@@ -106,16 +106,8 @@ export const Card = React.forwardRef<HTMLDivElement, CardProps>(
|
|
|
106
106
|
{header}
|
|
107
107
|
</h2>
|
|
108
108
|
)}
|
|
109
|
-
{metadata &&
|
|
110
|
-
|
|
111
|
-
{metadata}
|
|
112
|
-
</p>
|
|
113
|
-
)}
|
|
114
|
-
{children && (
|
|
115
|
-
<div className="text-sm leading-normal line-clamp-2 mb-3">
|
|
116
|
-
{children}
|
|
117
|
-
</div>
|
|
118
|
-
)}
|
|
109
|
+
{metadata && <p className="text-secondary text-xs mb-1">{metadata}</p>}
|
|
110
|
+
{children && <div className="text-sm leading-normal line-clamp-2 mb-3">{children}</div>}
|
|
119
111
|
{hasButtons && (
|
|
120
112
|
<div className="flex gap-2 flex-wrap mt-auto">
|
|
121
113
|
{button1 && renderButton(button1)}
|
|
@@ -124,7 +116,7 @@ export const Card = React.forwardRef<HTMLDivElement, CardProps>(
|
|
|
124
116
|
)}
|
|
125
117
|
</div>
|
|
126
118
|
</div>
|
|
127
|
-
)
|
|
119
|
+
);
|
|
128
120
|
}
|
|
129
|
-
)
|
|
130
|
-
Card.displayName =
|
|
121
|
+
);
|
|
122
|
+
Card.displayName = 'Card';
|
|
@@ -1,110 +1,100 @@
|
|
|
1
|
-
import * as React from
|
|
2
|
-
import useEmblaCarousel from
|
|
3
|
-
import { WheelGesturesPlugin } from
|
|
4
|
-
import { ArrowLeft, ArrowRight } from
|
|
5
|
-
import { useWidgetState, useDisplayMode } from
|
|
6
|
-
import { Button } from
|
|
7
|
-
import { cn } from
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import useEmblaCarousel from 'embla-carousel-react';
|
|
3
|
+
import { WheelGesturesPlugin } from 'embla-carousel-wheel-gestures';
|
|
4
|
+
import { ArrowLeft, ArrowRight } from '@openai/apps-sdk-ui/components/Icon';
|
|
5
|
+
import { useWidgetState, useDisplayMode } from 'sunpeak';
|
|
6
|
+
import { Button } from '@openai/apps-sdk-ui/components/Button';
|
|
7
|
+
import { cn } from '../../lib/index';
|
|
8
8
|
|
|
9
9
|
export interface CarouselState extends Record<string, unknown> {
|
|
10
|
-
currentIndex?: number
|
|
10
|
+
currentIndex?: number;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export type CarouselProps = {
|
|
14
|
-
children?: React.ReactNode
|
|
15
|
-
gap?: number
|
|
16
|
-
showArrows?: boolean
|
|
17
|
-
showEdgeGradients?: boolean
|
|
18
|
-
cardWidth?: number | { inline?: number; fullscreen?: number }
|
|
19
|
-
className?: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const Carousel = React.forwardRef<
|
|
23
|
-
HTMLDivElement,
|
|
24
|
-
CarouselProps
|
|
25
|
-
>(
|
|
14
|
+
children?: React.ReactNode;
|
|
15
|
+
gap?: number;
|
|
16
|
+
showArrows?: boolean;
|
|
17
|
+
showEdgeGradients?: boolean;
|
|
18
|
+
cardWidth?: number | { inline?: number; fullscreen?: number };
|
|
19
|
+
className?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const Carousel = React.forwardRef<HTMLDivElement, CarouselProps>(
|
|
26
23
|
(
|
|
27
|
-
{
|
|
28
|
-
children,
|
|
29
|
-
gap = 16,
|
|
30
|
-
showArrows = true,
|
|
31
|
-
showEdgeGradients = true,
|
|
32
|
-
cardWidth,
|
|
33
|
-
className,
|
|
34
|
-
},
|
|
24
|
+
{ children, gap = 16, showArrows = true, showEdgeGradients = true, cardWidth, className },
|
|
35
25
|
ref
|
|
36
26
|
) => {
|
|
37
27
|
const [widgetState, setWidgetState] = useWidgetState<CarouselState>(() => ({
|
|
38
28
|
currentIndex: 0,
|
|
39
|
-
}))
|
|
40
|
-
const displayMode = useDisplayMode()
|
|
29
|
+
}));
|
|
30
|
+
const displayMode = useDisplayMode();
|
|
41
31
|
|
|
42
32
|
const [emblaRef, emblaApi] = useEmblaCarousel(
|
|
43
33
|
{
|
|
44
|
-
align:
|
|
34
|
+
align: 'start',
|
|
45
35
|
dragFree: true,
|
|
46
|
-
containScroll:
|
|
36
|
+
containScroll: 'trimSnaps',
|
|
47
37
|
},
|
|
48
38
|
[WheelGesturesPlugin()]
|
|
49
|
-
)
|
|
39
|
+
);
|
|
50
40
|
|
|
51
|
-
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
|
|
52
|
-
const [canScrollNext, setCanScrollNext] = React.useState(false)
|
|
41
|
+
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
|
|
42
|
+
const [canScrollNext, setCanScrollNext] = React.useState(false);
|
|
53
43
|
|
|
54
44
|
const scrollPrev = React.useCallback(() => {
|
|
55
|
-
if (emblaApi) emblaApi.scrollPrev()
|
|
56
|
-
}, [emblaApi])
|
|
45
|
+
if (emblaApi) emblaApi.scrollPrev();
|
|
46
|
+
}, [emblaApi]);
|
|
57
47
|
|
|
58
48
|
const scrollNext = React.useCallback(() => {
|
|
59
|
-
if (emblaApi) emblaApi.scrollNext()
|
|
60
|
-
}, [emblaApi])
|
|
49
|
+
if (emblaApi) emblaApi.scrollNext();
|
|
50
|
+
}, [emblaApi]);
|
|
61
51
|
|
|
62
52
|
const onSelect = React.useCallback(() => {
|
|
63
|
-
if (!emblaApi) return
|
|
53
|
+
if (!emblaApi) return;
|
|
64
54
|
|
|
65
|
-
setCanScrollPrev(emblaApi.canScrollPrev())
|
|
66
|
-
setCanScrollNext(emblaApi.canScrollNext())
|
|
55
|
+
setCanScrollPrev(emblaApi.canScrollPrev());
|
|
56
|
+
setCanScrollNext(emblaApi.canScrollNext());
|
|
67
57
|
|
|
68
|
-
const currentIndex = emblaApi.selectedScrollSnap()
|
|
58
|
+
const currentIndex = emblaApi.selectedScrollSnap();
|
|
69
59
|
if (widgetState?.currentIndex !== currentIndex) {
|
|
70
|
-
setWidgetState({ currentIndex })
|
|
60
|
+
setWidgetState({ currentIndex });
|
|
71
61
|
}
|
|
72
|
-
}, [emblaApi, widgetState?.currentIndex, setWidgetState])
|
|
62
|
+
}, [emblaApi, widgetState?.currentIndex, setWidgetState]);
|
|
73
63
|
|
|
74
64
|
React.useEffect(() => {
|
|
75
|
-
if (!emblaApi) return
|
|
65
|
+
if (!emblaApi) return;
|
|
76
66
|
|
|
77
|
-
onSelect()
|
|
78
|
-
emblaApi.on(
|
|
79
|
-
emblaApi.on(
|
|
67
|
+
onSelect();
|
|
68
|
+
emblaApi.on('select', onSelect);
|
|
69
|
+
emblaApi.on('reInit', onSelect);
|
|
80
70
|
|
|
81
71
|
return () => {
|
|
82
|
-
emblaApi.off(
|
|
83
|
-
emblaApi.off(
|
|
84
|
-
}
|
|
85
|
-
}, [emblaApi, onSelect])
|
|
72
|
+
emblaApi.off('select', onSelect);
|
|
73
|
+
emblaApi.off('reInit', onSelect);
|
|
74
|
+
};
|
|
75
|
+
}, [emblaApi, onSelect]);
|
|
86
76
|
|
|
87
|
-
const childArray = React.Children.toArray(children)
|
|
77
|
+
const childArray = React.Children.toArray(children);
|
|
88
78
|
|
|
89
79
|
const getCardWidth = () => {
|
|
90
|
-
if (typeof cardWidth ===
|
|
91
|
-
return cardWidth
|
|
80
|
+
if (typeof cardWidth === 'number') {
|
|
81
|
+
return cardWidth;
|
|
92
82
|
}
|
|
93
|
-
if (cardWidth && typeof cardWidth ===
|
|
94
|
-
if (displayMode ===
|
|
95
|
-
return cardWidth.fullscreen
|
|
83
|
+
if (cardWidth && typeof cardWidth === 'object') {
|
|
84
|
+
if (displayMode === 'fullscreen' && cardWidth.fullscreen) {
|
|
85
|
+
return cardWidth.fullscreen;
|
|
96
86
|
}
|
|
97
87
|
if (cardWidth.inline) {
|
|
98
|
-
return cardWidth.inline
|
|
88
|
+
return cardWidth.inline;
|
|
99
89
|
}
|
|
100
90
|
}
|
|
101
|
-
return 220
|
|
102
|
-
}
|
|
91
|
+
return 220;
|
|
92
|
+
};
|
|
103
93
|
|
|
104
|
-
const cardWidthPx = getCardWidth()
|
|
94
|
+
const cardWidthPx = getCardWidth();
|
|
105
95
|
|
|
106
96
|
return (
|
|
107
|
-
<div ref={ref} className={cn(
|
|
97
|
+
<div ref={ref} className={cn('relative w-full', className)}>
|
|
108
98
|
{/* Left edge gradient */}
|
|
109
99
|
{showEdgeGradients && canScrollPrev && (
|
|
110
100
|
<div
|
|
@@ -172,7 +162,7 @@ export const Carousel = React.forwardRef<
|
|
|
172
162
|
</Button>
|
|
173
163
|
)}
|
|
174
164
|
</div>
|
|
175
|
-
)
|
|
165
|
+
);
|
|
176
166
|
}
|
|
177
|
-
)
|
|
178
|
-
Carousel.displayName =
|
|
167
|
+
);
|
|
168
|
+
Carousel.displayName = 'Carousel';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as React from
|
|
2
|
-
import { Albums } from
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Albums } from '../album/albums';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Production-ready Albums Resource
|
|
@@ -8,6 +8,6 @@ import { Albums } from "../album/albums"
|
|
|
8
8
|
* Can be dropped into any production environment without changes.
|
|
9
9
|
*/
|
|
10
10
|
export const AlbumsResource = React.forwardRef<HTMLDivElement>((_props, ref) => {
|
|
11
|
-
return <Albums ref={ref}
|
|
12
|
-
})
|
|
13
|
-
AlbumsResource.displayName =
|
|
11
|
+
return <Albums ref={ref} />;
|
|
12
|
+
});
|
|
13
|
+
AlbumsResource.displayName = 'AlbumsResource';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as React from
|
|
2
|
-
import { useWidgetProps } from
|
|
3
|
-
import { Carousel } from
|
|
4
|
-
import { Card } from
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { useWidgetProps } from 'sunpeak';
|
|
3
|
+
import { Carousel } from '../carousel/carousel';
|
|
4
|
+
import { Card } from '../card/card';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Production-ready Carousel Resource
|
|
@@ -11,21 +11,21 @@ import { Card } from "../card/card"
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
interface CarouselCard {
|
|
14
|
-
id: string
|
|
15
|
-
name: string
|
|
16
|
-
rating: number
|
|
17
|
-
category: string
|
|
18
|
-
location: string
|
|
19
|
-
image: string
|
|
20
|
-
description: string
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
rating: number;
|
|
17
|
+
category: string;
|
|
18
|
+
location: string;
|
|
19
|
+
image: string;
|
|
20
|
+
description: string;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
interface CarouselData extends Record<string, unknown> {
|
|
24
|
-
places: CarouselCard[]
|
|
24
|
+
places: CarouselCard[];
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export const CarouselResource = React.forwardRef<HTMLDivElement>((_props, ref) => {
|
|
28
|
-
const data = useWidgetProps<CarouselData>(() => ({ places: [] }))
|
|
28
|
+
const data = useWidgetProps<CarouselData>(() => ({ places: [] }));
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
31
|
<div ref={ref}>
|
|
@@ -40,12 +40,12 @@ export const CarouselResource = React.forwardRef<HTMLDivElement>((_props, ref) =
|
|
|
40
40
|
button1={{
|
|
41
41
|
isPrimary: true,
|
|
42
42
|
onClick: () => console.log(`Visit ${place.name}`),
|
|
43
|
-
children:
|
|
43
|
+
children: 'Visit',
|
|
44
44
|
}}
|
|
45
45
|
button2={{
|
|
46
46
|
isPrimary: false,
|
|
47
47
|
onClick: () => console.log(`Learn more about ${place.name}`),
|
|
48
|
-
children:
|
|
48
|
+
children: 'Learn More',
|
|
49
49
|
}}
|
|
50
50
|
>
|
|
51
51
|
{place.description}
|
|
@@ -53,6 +53,6 @@ export const CarouselResource = React.forwardRef<HTMLDivElement>((_props, ref) =
|
|
|
53
53
|
))}
|
|
54
54
|
</Carousel>
|
|
55
55
|
</div>
|
|
56
|
-
)
|
|
57
|
-
})
|
|
58
|
-
CarouselResource.displayName =
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
CarouselResource.displayName = 'CarouselResource';
|
|
@@ -33,55 +33,35 @@ export function CounterResource() {
|
|
|
33
33
|
return (
|
|
34
34
|
<div className="flex flex-col items-center justify-center p-8 space-y-6">
|
|
35
35
|
<div className="text-center space-y-2">
|
|
36
|
-
<h1 className="text-3xl font-bold text-primary">
|
|
37
|
-
|
|
38
|
-
</h1>
|
|
39
|
-
<p className="text-secondary">
|
|
40
|
-
Build your MCP resource here
|
|
41
|
-
</p>
|
|
36
|
+
<h1 className="text-3xl font-bold text-primary">Welcome to Sunpeak!</h1>
|
|
37
|
+
<p className="text-secondary">Build your MCP resource here</p>
|
|
42
38
|
</div>
|
|
43
39
|
|
|
44
40
|
<div className="flex flex-col items-center space-y-4 p-8 border border-subtle rounded-2xl bg-surface shadow-sm">
|
|
45
|
-
<div className="text-6xl font-bold text-primary">
|
|
46
|
-
{count}
|
|
47
|
-
</div>
|
|
41
|
+
<div className="text-6xl font-bold text-primary">{count}</div>
|
|
48
42
|
|
|
49
43
|
<div className="flex gap-2">
|
|
50
|
-
<Button
|
|
51
|
-
variant="soft"
|
|
52
|
-
color="secondary"
|
|
53
|
-
onClick={decrement}
|
|
54
|
-
aria-label="Decrement"
|
|
55
|
-
>
|
|
44
|
+
<Button variant="soft" color="secondary" onClick={decrement} aria-label="Decrement">
|
|
56
45
|
−
|
|
57
46
|
</Button>
|
|
58
|
-
<Button
|
|
59
|
-
variant="solid"
|
|
60
|
-
color="primary"
|
|
61
|
-
onClick={increment}
|
|
62
|
-
aria-label="Increment"
|
|
63
|
-
>
|
|
47
|
+
<Button variant="solid" color="primary" onClick={increment} aria-label="Increment">
|
|
64
48
|
+
|
|
65
49
|
</Button>
|
|
66
50
|
</div>
|
|
67
51
|
|
|
68
|
-
<Button
|
|
69
|
-
variant="outline"
|
|
70
|
-
color="secondary"
|
|
71
|
-
onClick={reset}
|
|
72
|
-
size="sm"
|
|
73
|
-
>
|
|
52
|
+
<Button variant="outline" color="secondary" onClick={reset} size="sm">
|
|
74
53
|
Reset
|
|
75
54
|
</Button>
|
|
76
55
|
</div>
|
|
77
56
|
|
|
78
57
|
<div className="text-center text-sm text-secondary max-w-md space-y-2">
|
|
79
58
|
<p>
|
|
80
|
-
This counter persists its state using
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
59
|
+
This counter persists its state using{' '}
|
|
60
|
+
<code className="px-1.5 py-0.5 rounded bg-surface-secondary text-primary">
|
|
61
|
+
useWidgetState
|
|
62
|
+
</code>
|
|
84
63
|
</p>
|
|
64
|
+
<p>Try switching between examples in the sidebar!</p>
|
|
85
65
|
</div>
|
|
86
66
|
</div>
|
|
87
67
|
);
|