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.
Files changed (43) hide show
  1. package/README.md +13 -11
  2. package/bin/sunpeak.js +3 -3
  3. package/dist/chatgpt/mock-openai.d.ts +7 -0
  4. package/dist/chatgpt/simple-sidebar.d.ts +38 -0
  5. package/dist/chatgpt/theme-provider.d.ts +2 -2
  6. package/dist/index.cjs +7733 -199
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.js +7734 -201
  9. package/dist/index.js.map +1 -1
  10. package/dist/mcp/index.cjs +80 -106
  11. package/dist/mcp/index.cjs.map +1 -1
  12. package/dist/mcp/index.js +80 -106
  13. package/dist/mcp/index.js.map +1 -1
  14. package/dist/style.css +2890 -315
  15. package/package.json +6 -5
  16. package/template/README.md +1 -0
  17. package/template/dev/main.tsx +6 -10
  18. package/template/package.json +5 -4
  19. package/template/scripts/build-all.mjs +19 -10
  20. package/template/scripts/validate.mjs +8 -2
  21. package/template/src/components/album/album-card.test.tsx +62 -0
  22. package/template/src/components/album/album-card.tsx +14 -16
  23. package/template/src/components/album/albums.test.tsx +88 -0
  24. package/template/src/components/album/albums.tsx +50 -64
  25. package/template/src/components/album/film-strip.test.tsx +64 -0
  26. package/template/src/components/album/film-strip.tsx +16 -16
  27. package/template/src/components/album/fullscreen-viewer.test.tsx +77 -0
  28. package/template/src/components/album/fullscreen-viewer.tsx +45 -50
  29. package/template/src/components/card/card.test.tsx +1 -4
  30. package/template/src/components/card/card.tsx +38 -46
  31. package/template/src/components/carousel/carousel.tsx +57 -67
  32. package/template/src/components/resources/{AlbumsResource.tsx → albums-resource.tsx} +5 -5
  33. package/template/src/components/resources/{CarouselResource.tsx → carousel-resource.tsx} +18 -18
  34. package/template/src/components/resources/{CounterResource.tsx → counter-resource.tsx} +11 -31
  35. package/template/src/components/resources/index.ts +3 -3
  36. package/template/src/simulations/albums-simulation.ts +71 -71
  37. package/template/src/simulations/carousel-simulation.ts +34 -34
  38. package/template/src/simulations/counter-simulation.ts +2 -2
  39. package/template/vite.config.build.ts +2 -2
  40. package/template/vite.config.ts +1 -1
  41. package/template/vitest.config.ts +1 -1
  42. package/dist/runtime/index.d.ts +0 -7
  43. /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 "react"
2
- import { useMaxHeight } from "sunpeak"
3
- import { cn } from "../../lib/index"
4
- import { FilmStrip } from "./film-strip"
5
- import type { Album } from "./albums"
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
- HTMLDivElement,
14
- FullscreenViewerProps
15
- >(({ album, className }, ref) => {
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
- React.useEffect(() => {
20
- setSelectedIndex(0)
21
- }, [album?.id])
17
+ React.useEffect(() => {
18
+ setSelectedIndex(0);
19
+ }, [album?.id]);
22
20
 
23
- const selectedPhoto = album?.photos?.[selectedIndex]
21
+ const selectedPhoto = album?.photos?.[selectedIndex];
24
22
 
25
- return (
26
- <div
27
- ref={ref}
28
- className={cn("relative w-full h-full bg-surface", className)}
29
- style={{
30
- maxHeight: maxHeight ?? undefined,
31
- height: maxHeight ?? undefined,
32
- }}
33
- >
34
- <div className="absolute inset-0 flex flex-row overflow-hidden">
35
- {/* Film strip */}
36
- <div className="hidden md:block absolute pointer-events-none z-10 left-0 top-0 bottom-0 w-40">
37
- <FilmStrip
38
- album={album}
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
- {/* Main photo */}
45
- <div className="flex-1 min-w-0 px-40 py-10 relative flex items-center justify-center">
46
- <div className="relative w-full h-full">
47
- {selectedPhoto ? (
48
- <img
49
- src={selectedPhoto.url}
50
- alt={selectedPhoto.title || album.title}
51
- className="absolute inset-0 m-auto rounded-3xl shadow-sm border border-primary/10 max-w-full max-h-full object-contain"
52
- />
53
- ) : null}
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
- </div>
58
- )
59
- })
60
- FullscreenViewer.displayName = "FullscreenViewer"
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 "react"
2
- import { Button } from "@openai/apps-sdk-ui/components/Button"
3
- import { cn } from "../../lib/index"
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?: "default" | "bordered" | "elevated"
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 = "default",
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: "border border-subtle bg-surface",
45
- bordered: "border-2 border-default bg-surface",
46
- elevated: "border border-subtle bg-surface shadow-lg",
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 ? "primary" : "secondary"}
64
- variant={isPrimary ? "solid" : "soft"}
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
- "overflow-hidden rounded-2xl cursor-pointer select-none",
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
- <p className="text-secondary text-xs mb-1">
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 = "Card"
121
+ );
122
+ Card.displayName = 'Card';
@@ -1,110 +1,100 @@
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"
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: "start",
34
+ align: 'start',
45
35
  dragFree: true,
46
- containScroll: "trimSnaps",
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("select", onSelect)
79
- emblaApi.on("reInit", onSelect)
67
+ onSelect();
68
+ emblaApi.on('select', onSelect);
69
+ emblaApi.on('reInit', onSelect);
80
70
 
81
71
  return () => {
82
- emblaApi.off("select", onSelect)
83
- emblaApi.off("reInit", onSelect)
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 === "number") {
91
- return cardWidth
80
+ if (typeof cardWidth === 'number') {
81
+ return cardWidth;
92
82
  }
93
- if (cardWidth && typeof cardWidth === "object") {
94
- if (displayMode === "fullscreen" && cardWidth.fullscreen) {
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("relative w-full", className)}>
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 = "Carousel"
167
+ );
168
+ Carousel.displayName = 'Carousel';
@@ -1,5 +1,5 @@
1
- import * as React from "react"
2
- import { Albums } from "../album/albums"
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 = "AlbumsResource"
11
+ return <Albums ref={ref} />;
12
+ });
13
+ AlbumsResource.displayName = 'AlbumsResource';
@@ -1,7 +1,7 @@
1
- import * as React from "react"
2
- import { useWidgetProps } from "sunpeak"
3
- import { Carousel } from "../carousel/carousel"
4
- import { Card } from "../card/card"
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: "Visit",
43
+ children: 'Visit',
44
44
  }}
45
45
  button2={{
46
46
  isPrimary: false,
47
47
  onClick: () => console.log(`Learn more about ${place.name}`),
48
- children: "Learn More",
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 = "CarouselResource"
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
- Welcome to Sunpeak!
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 <code className="px-1.5 py-0.5 rounded bg-surface-secondary text-primary">useWidgetState</code>
81
- </p>
82
- <p>
83
- Try switching between examples in the sidebar!
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
  );