sunpeak 0.3.8 → 0.4.2

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 (32) hide show
  1. package/README.md +5 -5
  2. package/dist/chatgpt/chatgpt-simulator.d.ts +13 -3
  3. package/dist/chatgpt/index.d.ts +1 -0
  4. package/dist/index.cjs +24 -6
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.js +24 -6
  7. package/dist/index.js.map +1 -1
  8. package/dist/style.css +179 -7
  9. package/package.json +1 -1
  10. package/template/data/albums.json +112 -0
  11. package/template/data/places.json +49 -0
  12. package/template/dev/main.tsx +4 -59
  13. package/template/index.html +1 -1
  14. package/template/mcp/server.ts +4 -50
  15. package/template/src/App.tsx +87 -39
  16. package/template/src/components/album/album-card.tsx +45 -0
  17. package/template/src/components/album/albums.tsx +77 -0
  18. package/template/src/components/album/film-strip.tsx +50 -0
  19. package/template/src/components/album/fullscreen-viewer.tsx +60 -0
  20. package/template/src/components/album/index.ts +4 -0
  21. package/template/src/components/{openai-card.test.tsx → card/card.test.tsx} +12 -12
  22. package/template/src/components/{openai-card.tsx → card/card.tsx} +8 -8
  23. package/template/src/components/card/index.ts +1 -0
  24. package/template/src/components/{openai-carousel.test.tsx → carousel/carousel.test.tsx} +10 -10
  25. package/template/src/components/{openai-carousel.tsx → carousel/carousel.tsx} +7 -7
  26. package/template/src/components/carousel/index.ts +1 -0
  27. package/template/src/components/index.ts +4 -2
  28. package/template/src/components/simulations/albums-simulation.tsx +20 -0
  29. package/template/src/components/simulations/app-simulation.tsx +13 -0
  30. package/template/src/components/simulations/carousel-simulation.tsx +63 -0
  31. package/template/src/components/simulations/index.tsx +14 -0
  32. package/template/src/styles/globals.css +2 -0
@@ -1,59 +1,13 @@
1
1
  import { runMCPServer } from 'sunpeak/mcp';
2
2
  import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
+ import { readFileSync } from 'fs';
4
5
 
5
6
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
7
 
7
- // Dummy data matching the dev script
8
- const toolOutput = {
9
- places: [
10
- {
11
- id: '1',
12
- name: 'Lady Bird Lake',
13
- rating: 4.5,
14
- category: 'Waterfront',
15
- location: 'Austin',
16
- image: 'https://images.unsplash.com/photo-1520950237264-dfe336995c34?w=400&h=400&fit=crop',
17
- description: 'Scenic lake perfect for kayaking, paddleboarding, and trails.',
18
- },
19
- {
20
- id: '2',
21
- name: 'Texas State Capitol',
22
- rating: 4.8,
23
- category: 'Historic Site',
24
- location: 'Austin',
25
- image: 'https://images.unsplash.com/photo-1664231978322-4d0b45c7027b?w=400&h=400&fit=crop',
26
- description: 'Stunning capitol building with free tours and beautiful grounds.',
27
- },
28
- {
29
- id: '3',
30
- name: 'The Paramount Theatre',
31
- rating: 4.7,
32
- category: 'Architecture',
33
- location: 'Austin',
34
- image: 'https://images.unsplash.com/photo-1583097090970-4d3b940ea1a0?w=400&h=400&fit=crop',
35
- description: 'Century-old performance and movie theatre in the heart of downtown Austin.',
36
- },
37
- {
38
- id: '4',
39
- name: 'Zilker Park',
40
- rating: 4.7,
41
- category: 'Park',
42
- location: 'Austin',
43
- image: 'https://images.unsplash.com/photo-1563828568124-f800803ba13c?w=400&h=400&fit=crop',
44
- description: 'Popular park with trails, sports fields, and Barton Springs Pool.',
45
- },
46
- {
47
- id: '5',
48
- name: 'South Congress Avenue',
49
- rating: 4.6,
50
- category: 'Landmark',
51
- location: 'Austin',
52
- image: 'https://images.unsplash.com/photo-1588993608283-7f0eda4438be?w=400&h=400&fit=crop',
53
- description: 'Vibrant street with unique shops, restaurants, and live music.',
54
- },
55
- ],
56
- };
8
+ // Load places data from JSON file
9
+ const placesDataPath = path.resolve(__dirname, '../data/places.json');
10
+ const toolOutput = JSON.parse(readFileSync(placesDataPath, 'utf-8'));
57
11
 
58
12
  runMCPServer({
59
13
  name: 'my-sunpeak-app',
@@ -1,49 +1,97 @@
1
1
  import './styles/globals.css';
2
- import 'sunpeak/style.css';
3
-
4
- import { useWidgetProps } from 'sunpeak';
5
- import { OpenAICarousel, OpenAICard } from './components';
6
-
7
- export interface Place {
8
- id: string;
9
- name: string;
10
- rating: number;
11
- category: string;
12
- location: string;
13
- image: string;
14
- description: string;
15
- }
16
2
 
17
- export interface AppData extends Record<string, unknown> {
18
- places: Place[];
3
+ import { useWidgetState } from 'sunpeak';
4
+ import { Button } from '@openai/apps-sdk-ui/components/Button';
5
+
6
+ interface CounterState extends Record<string, unknown> {
7
+ count?: number;
19
8
  }
20
9
 
10
+ /**
11
+ * Welcome to your Sunpeak App!
12
+ *
13
+ * This is a simple counter app to get you started.
14
+ * Try building your own app here!
15
+ *
16
+ * Tips:
17
+ * - Use the Component dropdown in the sidebar to see example apps
18
+ * - Check out the components folder for reusable components
19
+ * - Use sunpeak hooks for state management and display modes
20
+ * - Edit this file and see your changes live
21
+ * - Edit ./components/simulations/app-simulation.tsx to customize your simulation
22
+ */
21
23
  export function App() {
22
- const data = useWidgetProps<AppData>(() => ({ places: [] }));
24
+ const [widgetState, setWidgetState] = useWidgetState<CounterState>(() => ({
25
+ count: 0,
26
+ }));
27
+
28
+ const count = widgetState?.count ?? 0;
29
+
30
+ const increment = () => {
31
+ setWidgetState({ count: count + 1 });
32
+ };
33
+
34
+ const decrement = () => {
35
+ setWidgetState({ count: count - 1 });
36
+ };
37
+
38
+ const reset = () => {
39
+ setWidgetState({ count: 0 });
40
+ };
23
41
 
24
42
  return (
25
- <OpenAICarousel gap={16} showArrows={true} showEdgeGradients={true} cardWidth={220}>
26
- {data.places.map((place) => (
27
- <OpenAICard
28
- key={place.id}
29
- image={place.image}
30
- imageAlt={place.name}
31
- header={place.name}
32
- metadata={`⭐ ${place.rating} • ${place.category} • ${place.location}`}
33
- button1={{
34
- isPrimary: true,
35
- onClick: () => console.log(`Visit ${place.name}`),
36
- children: 'Visit',
37
- }}
38
- button2={{
39
- isPrimary: false,
40
- onClick: () => console.log(`Learn more about ${place.name}`),
41
- children: 'Learn More',
42
- }}
43
+ <div className="flex flex-col items-center justify-center p-8 space-y-6">
44
+ <div className="text-center space-y-2">
45
+ <h1 className="text-3xl font-bold text-primary">
46
+ Welcome to Sunpeak!
47
+ </h1>
48
+ <p className="text-secondary">
49
+ Build your ChatGPT app here
50
+ </p>
51
+ </div>
52
+
53
+ <div className="flex flex-col items-center space-y-4 p-8 border border-subtle rounded-2xl bg-surface shadow-sm">
54
+ <div className="text-6xl font-bold text-primary">
55
+ {count}
56
+ </div>
57
+
58
+ <div className="flex gap-2">
59
+ <Button
60
+ variant="soft"
61
+ color="secondary"
62
+ onClick={decrement}
63
+ aria-label="Decrement"
64
+ >
65
+
66
+ </Button>
67
+ <Button
68
+ variant="solid"
69
+ color="primary"
70
+ onClick={increment}
71
+ aria-label="Increment"
72
+ >
73
+ +
74
+ </Button>
75
+ </div>
76
+
77
+ <Button
78
+ variant="outline"
79
+ color="secondary"
80
+ onClick={reset}
81
+ size="sm"
43
82
  >
44
- {place.description}
45
- </OpenAICard>
46
- ))}
47
- </OpenAICarousel>
83
+ Reset
84
+ </Button>
85
+ </div>
86
+
87
+ <div className="text-center text-sm text-secondary max-w-md space-y-2">
88
+ <p>
89
+ This counter persists its state using <code className="px-1.5 py-0.5 rounded bg-surface-secondary text-primary">useWidgetState</code>
90
+ </p>
91
+ <p>
92
+ Try switching between examples in the sidebar!
93
+ </p>
94
+ </div>
95
+ </div>
48
96
  );
49
97
  }
@@ -0,0 +1,45 @@
1
+ import * as React from "react"
2
+ import { Button } from "@openai/apps-sdk-ui/components/Button"
3
+ import { cn } from "../../lib/index"
4
+ import type { Album } from "./albums"
5
+
6
+ export type AlbumCardProps = {
7
+ album: Album
8
+ onSelect?: (album: Album) => void
9
+ className?: string
10
+ }
11
+
12
+ export const AlbumCard = React.forwardRef<HTMLButtonElement, AlbumCardProps>(
13
+ ({ album, onSelect, className }, ref) => {
14
+ return (
15
+ <Button
16
+ ref={ref}
17
+ variant="ghost"
18
+ color="secondary"
19
+ className={cn(
20
+ "rounded-xl flex-shrink-0 w-full h-full p-0 text-left flex flex-col [&:hover]:bg-transparent hover:bg-transparent cursor-pointer",
21
+ className
22
+ )}
23
+ onClick={() => onSelect?.(album)}
24
+ >
25
+ <div className="aspect-[4/3] w-full overflow-hidden rounded-xl flex-shrink-0">
26
+ <img
27
+ src={album.cover}
28
+ alt={album.title}
29
+ className="w-full h-full object-cover"
30
+ loading="lazy"
31
+ />
32
+ </div>
33
+ <div className="flex-shrink-0 w-full p-2">
34
+ <div className="text-base font-normal text-primary">
35
+ {album.title}
36
+ </div>
37
+ <div className="text-sm text-secondary">
38
+ {album.photos.length} {album.photos.length === 1 ? "photo" : "photos"}
39
+ </div>
40
+ </div>
41
+ </Button>
42
+ )
43
+ }
44
+ )
45
+ AlbumCard.displayName = "AlbumCard"
@@ -0,0 +1,77 @@
1
+ import * as React from "react"
2
+ import { useWidgetState, useDisplayMode, useWidgetAPI, useWidgetProps } from "sunpeak"
3
+ import { Carousel } from "../carousel"
4
+ import { AlbumCard } from "./album-card"
5
+ import { FullscreenViewer } from "./fullscreen-viewer"
6
+
7
+ export interface Album {
8
+ id: string
9
+ title: string
10
+ cover: string
11
+ photos: Array<{
12
+ id: string
13
+ title: string
14
+ url: string
15
+ }>
16
+ }
17
+
18
+ export interface OpenAIAlbumsData extends Record<string, unknown> {
19
+ albums: Album[]
20
+ }
21
+
22
+ export interface OpenAIAlbumsState extends Record<string, unknown> {
23
+ selectedAlbumId?: string | null
24
+ }
25
+
26
+ export type OpenAIAlbumsProps = {
27
+ className?: string
28
+ }
29
+
30
+ export const OpenAIAlbums = React.forwardRef<HTMLDivElement, OpenAIAlbumsProps>(
31
+ ({ className }, ref) => {
32
+ const data = useWidgetProps<OpenAIAlbumsData>(() => ({ albums: [] }))
33
+ const [widgetState, setWidgetState] = useWidgetState<OpenAIAlbumsState>(() => ({
34
+ selectedAlbumId: null,
35
+ }))
36
+ const displayMode = useDisplayMode()
37
+ const api = useWidgetAPI()
38
+
39
+ const albums = data.albums || []
40
+ const selectedAlbum = albums.find(
41
+ (album) => album.id === widgetState?.selectedAlbumId
42
+ )
43
+
44
+ const handleSelectAlbum = React.useCallback(
45
+ (album: Album) => {
46
+ setWidgetState({ selectedAlbumId: album.id })
47
+ api?.requestDisplayMode?.({ mode: "fullscreen" })
48
+ },
49
+ [setWidgetState, api]
50
+ )
51
+
52
+ if (displayMode === "fullscreen" && selectedAlbum) {
53
+ return (
54
+ <FullscreenViewer
55
+ ref={ref}
56
+ album={selectedAlbum}
57
+ />
58
+ )
59
+ }
60
+
61
+ return (
62
+ <div ref={ref} className={className}>
63
+ <Carousel
64
+ gap={20}
65
+ showArrows={false}
66
+ showEdgeGradients={false}
67
+ cardWidth={272}
68
+ >
69
+ {albums.map((album) => (
70
+ <AlbumCard key={album.id} album={album} onSelect={handleSelectAlbum} />
71
+ ))}
72
+ </Carousel>
73
+ </div>
74
+ )
75
+ }
76
+ )
77
+ OpenAIAlbums.displayName = "OpenAIAlbums"
@@ -0,0 +1,50 @@
1
+ import * as React from "react"
2
+ import { Button } from "@openai/apps-sdk-ui/components/Button"
3
+ import { cn } from "../../lib/index"
4
+ import type { Album } from "./albums"
5
+
6
+ export type FilmStripProps = {
7
+ album: Album
8
+ selectedIndex: number
9
+ onSelect?: (index: number) => void
10
+ className?: string
11
+ }
12
+
13
+ export const FilmStrip = React.forwardRef<HTMLDivElement, FilmStripProps>(
14
+ ({ album, selectedIndex, onSelect, className }, ref) => {
15
+ return (
16
+ <div
17
+ ref={ref}
18
+ className={cn(
19
+ "h-full w-full overflow-auto flex flex-col items-center justify-center p-5 space-y-5",
20
+ className
21
+ )}
22
+ >
23
+ {album.photos.map((photo, idx) => (
24
+ <Button
25
+ key={photo.id}
26
+ variant="ghost"
27
+ color="secondary"
28
+ onClick={() => onSelect?.(idx)}
29
+ className={cn(
30
+ "block w-full h-auto p-[1px] pointer-events-auto rounded-[10px] border transition-all",
31
+ idx === selectedIndex
32
+ ? "border-primary shadow-md"
33
+ : "border-transparent hover:border-primary/30 opacity-60 hover:opacity-100"
34
+ )}
35
+ >
36
+ <div className="aspect-[5/3] rounded-lg overflow-hidden w-full">
37
+ <img
38
+ src={photo.url}
39
+ alt={photo.title || `Photo ${idx + 1}`}
40
+ className="h-full w-full object-cover"
41
+ loading="lazy"
42
+ />
43
+ </div>
44
+ </Button>
45
+ ))}
46
+ </div>
47
+ )
48
+ }
49
+ )
50
+ FilmStrip.displayName = "FilmStrip"
@@ -0,0 +1,60 @@
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
+
7
+ export type FullscreenViewerProps = {
8
+ album: Album
9
+ className?: string
10
+ }
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)
18
+
19
+ React.useEffect(() => {
20
+ setSelectedIndex(0)
21
+ }, [album?.id])
22
+
23
+ const selectedPhoto = album?.photos?.[selectedIndex]
24
+
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>
43
+
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}
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </div>
58
+ )
59
+ })
60
+ FullscreenViewer.displayName = "FullscreenViewer"
@@ -0,0 +1,4 @@
1
+ export * from './album-card';
2
+ export * from './albums';
3
+ export * from './fullscreen-viewer';
4
+ export * from './film-strip';
@@ -1,29 +1,29 @@
1
1
  import { render, screen, fireEvent } from '@testing-library/react';
2
2
  import { describe, it, expect, vi } from 'vitest';
3
- import { OpenAICard } from './openai-card';
3
+ import { Card } from './card';
4
4
 
5
- describe('OpenAICard', () => {
5
+ describe('Card', () => {
6
6
  it('renders correct variant classes', () => {
7
7
  const { container, rerender } = render(
8
- <OpenAICard variant="default" data-testid="card">
8
+ <Card variant="default" data-testid="card">
9
9
  Content
10
- </OpenAICard>
10
+ </Card>
11
11
  );
12
12
 
13
13
  const card = container.firstChild as HTMLElement;
14
14
  expect(card.className).toContain('border border-subtle bg-surface');
15
15
 
16
16
  rerender(
17
- <OpenAICard variant="bordered" data-testid="card">
17
+ <Card variant="bordered" data-testid="card">
18
18
  Content
19
- </OpenAICard>
19
+ </Card>
20
20
  );
21
21
  expect(card.className).toContain('border-2 border-default bg-surface');
22
22
 
23
23
  rerender(
24
- <OpenAICard variant="elevated" data-testid="card">
24
+ <Card variant="elevated" data-testid="card">
25
25
  Content
26
- </OpenAICard>
26
+ </Card>
27
27
  );
28
28
  expect(card.className).toContain('shadow-lg');
29
29
  });
@@ -33,12 +33,12 @@ describe('OpenAICard', () => {
33
33
  const button1OnClick = vi.fn();
34
34
 
35
35
  render(
36
- <OpenAICard
36
+ <Card
37
37
  onClick={cardOnClick}
38
38
  button1={{ onClick: button1OnClick, children: 'Click Me' }}
39
39
  >
40
40
  Content
41
- </OpenAICard>
41
+ </Card>
42
42
  );
43
43
 
44
44
  const button = screen.getByText('Click Me');
@@ -53,12 +53,12 @@ describe('OpenAICard', () => {
53
53
  const button2OnClick = vi.fn();
54
54
 
55
55
  render(
56
- <OpenAICard
56
+ <Card
57
57
  button1={{ onClick: button1OnClick, children: 'Button 1', isPrimary: true }}
58
58
  button2={{ onClick: button2OnClick, children: 'Button 2' }}
59
59
  >
60
60
  Content
61
- </OpenAICard>
61
+ </Card>
62
62
  );
63
63
 
64
64
  const button1 = screen.getByText('Button 1');
@@ -1,14 +1,14 @@
1
1
  import * as React from "react"
2
2
  import { Button } from "@openai/apps-sdk-ui/components/Button"
3
- import { cn } from "../lib/index"
3
+ import { cn } from "../../lib/index"
4
4
 
5
- export interface OpenAIButtonProps {
5
+ export interface CardButtonProps {
6
6
  isPrimary?: boolean
7
7
  onClick: () => void
8
8
  children: React.ReactNode
9
9
  }
10
10
 
11
- export interface OpenAICardProps extends React.HTMLAttributes<HTMLDivElement> {
11
+ export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
12
12
  children?: React.ReactNode
13
13
  image?: string
14
14
  imageAlt?: string
@@ -16,12 +16,12 @@ export interface OpenAICardProps extends React.HTMLAttributes<HTMLDivElement> {
16
16
  imageMaxHeight?: number
17
17
  header?: React.ReactNode
18
18
  metadata?: React.ReactNode
19
- button1?: OpenAIButtonProps
20
- button2?: OpenAIButtonProps
19
+ button1?: CardButtonProps
20
+ button2?: CardButtonProps
21
21
  variant?: "default" | "bordered" | "elevated"
22
22
  }
23
23
 
24
- export const OpenAICard = React.forwardRef<HTMLDivElement, OpenAICardProps>(
24
+ export const Card = React.forwardRef<HTMLDivElement, CardProps>(
25
25
  (
26
26
  {
27
27
  children,
@@ -50,7 +50,7 @@ export const OpenAICard = React.forwardRef<HTMLDivElement, OpenAICardProps>(
50
50
  onClick?.(e)
51
51
  }
52
52
 
53
- const renderButton = (buttonProps: OpenAIButtonProps) => {
53
+ const renderButton = (buttonProps: CardButtonProps) => {
54
54
  const { isPrimary = false, onClick: buttonOnClick, children } = buttonProps
55
55
 
56
56
  const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
@@ -123,4 +123,4 @@ export const OpenAICard = React.forwardRef<HTMLDivElement, OpenAICardProps>(
123
123
  )
124
124
  }
125
125
  )
126
- OpenAICard.displayName = "OpenAICard"
126
+ Card.displayName = "Card"
@@ -0,0 +1 @@
1
+ export * from './card';
@@ -1,6 +1,6 @@
1
1
  import { render } from '@testing-library/react';
2
2
  import { describe, it, expect, vi, beforeEach } from 'vitest';
3
- import { OpenAICarousel } from './openai-carousel';
3
+ import { Carousel } from './carousel';
4
4
 
5
5
  const mockUseDisplayMode = vi.fn(() => 'inline');
6
6
 
@@ -20,18 +20,18 @@ vi.mock('embla-carousel-wheel-gestures', () => ({
20
20
  WheelGesturesPlugin: vi.fn(() => ({})),
21
21
  }));
22
22
 
23
- describe('OpenAICarousel', () => {
23
+ describe('Carousel', () => {
24
24
  beforeEach(() => {
25
25
  mockUseDisplayMode.mockReturnValue('inline');
26
26
  });
27
27
 
28
28
  it('renders all children with correct card width', () => {
29
29
  const { container } = render(
30
- <OpenAICarousel cardWidth={300}>
30
+ <Carousel cardWidth={300}>
31
31
  <div>Card 1</div>
32
32
  <div>Card 2</div>
33
33
  <div>Card 3</div>
34
- </OpenAICarousel>
34
+ </Carousel>
35
35
  );
36
36
 
37
37
  const cardContainers = container.querySelectorAll('.flex-none');
@@ -48,9 +48,9 @@ describe('OpenAICarousel', () => {
48
48
  // Test inline mode
49
49
  mockUseDisplayMode.mockReturnValue('inline');
50
50
  const { container: inlineContainer } = render(
51
- <OpenAICarousel cardWidth={{ inline: 250, fullscreen: 400 }}>
51
+ <Carousel cardWidth={{ inline: 250, fullscreen: 400 }}>
52
52
  <div>Card 1</div>
53
- </OpenAICarousel>
53
+ </Carousel>
54
54
  );
55
55
 
56
56
  let cardContainer = inlineContainer.querySelector('.flex-none') as HTMLElement;
@@ -59,9 +59,9 @@ describe('OpenAICarousel', () => {
59
59
  // Test fullscreen mode
60
60
  mockUseDisplayMode.mockReturnValue('fullscreen');
61
61
  const { container: fullscreenContainer } = render(
62
- <OpenAICarousel cardWidth={{ inline: 250, fullscreen: 400 }}>
62
+ <Carousel cardWidth={{ inline: 250, fullscreen: 400 }}>
63
63
  <div>Card 1</div>
64
- </OpenAICarousel>
64
+ </Carousel>
65
65
  );
66
66
 
67
67
  cardContainer = fullscreenContainer.querySelector('.flex-none') as HTMLElement;
@@ -70,10 +70,10 @@ describe('OpenAICarousel', () => {
70
70
 
71
71
  it('applies custom gap between cards', () => {
72
72
  const { container } = render(
73
- <OpenAICarousel gap={24}>
73
+ <Carousel gap={24}>
74
74
  <div>Card 1</div>
75
75
  <div>Card 2</div>
76
- </OpenAICarousel>
76
+ </Carousel>
77
77
  );
78
78
 
79
79
  const carouselTrack = container.querySelector('.flex.touch-pan-y') as HTMLElement;
@@ -4,13 +4,13 @@ import { WheelGesturesPlugin } from "embla-carousel-wheel-gestures"
4
4
  import { ArrowLeft, ArrowRight } from "@openai/apps-sdk-ui/components/Icon"
5
5
  import { useWidgetState, useDisplayMode } from "sunpeak"
6
6
  import { Button } from "@openai/apps-sdk-ui/components/Button"
7
- import { cn } from "../lib/index"
7
+ import { cn } from "../../lib/index"
8
8
 
9
- export interface OpenAICarouselState extends Record<string, unknown> {
9
+ export interface CarouselState extends Record<string, unknown> {
10
10
  currentIndex?: number
11
11
  }
12
12
 
13
- export type OpenAICarouselProps = {
13
+ export type CarouselProps = {
14
14
  children?: React.ReactNode
15
15
  gap?: number
16
16
  showArrows?: boolean
@@ -19,9 +19,9 @@ export type OpenAICarouselProps = {
19
19
  className?: string
20
20
  }
21
21
 
22
- export const OpenAICarousel = React.forwardRef<
22
+ export const Carousel = React.forwardRef<
23
23
  HTMLDivElement,
24
- OpenAICarouselProps
24
+ CarouselProps
25
25
  >(
26
26
  (
27
27
  {
@@ -34,7 +34,7 @@ export const OpenAICarousel = React.forwardRef<
34
34
  },
35
35
  ref
36
36
  ) => {
37
- const [widgetState, setWidgetState] = useWidgetState<OpenAICarouselState>(() => ({
37
+ const [widgetState, setWidgetState] = useWidgetState<CarouselState>(() => ({
38
38
  currentIndex: 0,
39
39
  }))
40
40
  const displayMode = useDisplayMode()
@@ -175,4 +175,4 @@ export const OpenAICarousel = React.forwardRef<
175
175
  )
176
176
  }
177
177
  )
178
- OpenAICarousel.displayName = "OpenAICarousel"
178
+ Carousel.displayName = "Carousel"
@@ -0,0 +1 @@
1
+ export * from './carousel';
@@ -1,2 +1,4 @@
1
- export * from './openai-carousel';
2
- export * from './openai-card';
1
+ export * from './card';
2
+ export * from './carousel';
3
+ export * from './album';
4
+ export * from './simulations';