sunpeak 0.6.1 → 0.6.4

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 (40) hide show
  1. package/bin/sunpeak.js +129 -6
  2. package/dist/chatgpt/conversation.d.ts +2 -1
  3. package/dist/index.cjs +24 -4
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.js +24 -4
  6. package/dist/index.js.map +1 -1
  7. package/dist/mcp/entry.cjs +2 -2
  8. package/dist/mcp/entry.cjs.map +1 -1
  9. package/dist/mcp/entry.js +2 -2
  10. package/dist/mcp/entry.js.map +1 -1
  11. package/dist/mcp/index.cjs +1 -1
  12. package/dist/mcp/index.js +1 -1
  13. package/dist/{server-DpriZ4jT.cjs → server-CQGbJWbk.cjs} +17 -8
  14. package/dist/{server-DpriZ4jT.cjs.map → server-CQGbJWbk.cjs.map} +1 -1
  15. package/dist/{server-SBlanUcf.js → server-DGCvp1RA.js} +17 -8
  16. package/dist/{server-SBlanUcf.js.map → server-DGCvp1RA.js.map} +1 -1
  17. package/dist/style.css +4 -0
  18. package/package.json +1 -1
  19. package/template/.sunpeak/dev.tsx +1 -1
  20. package/template/dist/chatgpt/albums.js +2 -2
  21. package/template/dist/chatgpt/carousel.js +1 -1
  22. package/template/dist/chatgpt/counter.js +1 -1
  23. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Button.js +2 -2
  24. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Select.js +9 -9
  25. package/template/node_modules/.vite/deps/_metadata.json +22 -22
  26. package/template/node_modules/.vite/deps/{chunk-DQAZDQU3.js → chunk-EVJ3DVH5.js} +5 -5
  27. package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  28. package/template/src/components/album/album-carousel.test.tsx +84 -0
  29. package/template/src/components/album/album-carousel.tsx +168 -0
  30. package/template/src/components/album/albums.test.tsx +2 -2
  31. package/template/src/components/album/albums.tsx +3 -3
  32. package/template/src/components/album/index.ts +1 -0
  33. package/template/src/components/carousel/index.ts +1 -0
  34. package/template/src/components/index.ts +0 -1
  35. package/template/src/resources/carousel-resource.test.tsx +1 -4
  36. package/template/src/resources/carousel-resource.tsx +1 -2
  37. package/template/src/components/card/index.ts +0 -1
  38. /package/template/node_modules/.vite/deps/{chunk-DQAZDQU3.js.map → chunk-EVJ3DVH5.js.map} +0 -0
  39. /package/template/src/components/{card → carousel}/card.test.tsx +0 -0
  40. /package/template/src/components/{card → carousel}/card.tsx +0 -0
@@ -2,10 +2,10 @@ import {
2
2
  Button,
3
3
  ButtonLink,
4
4
  CopyButton
5
- } from "./chunk-DQAZDQU3.js";
6
- import "./chunk-XB525PXG.js";
5
+ } from "./chunk-EVJ3DVH5.js";
7
6
  import "./chunk-YOJ6QPGS.js";
8
7
  import "./chunk-BAG6OO6S.js";
8
+ import "./chunk-XB525PXG.js";
9
9
  import "./chunk-QPJAV452.js";
10
10
  import "./chunk-EGRHWZRV.js";
11
11
  import "./chunk-CNYJBM5F.js";
@@ -2,15 +2,7 @@ import {
2
2
  Button,
3
3
  LoadingIndicator,
4
4
  TransitionGroup
5
- } from "./chunk-DQAZDQU3.js";
6
- import {
7
- Check_default,
8
- ChevronDownVector_default,
9
- DropdownVector_default,
10
- Info_default,
11
- Search_default,
12
- X_default
13
- } from "./chunk-XB525PXG.js";
5
+ } from "./chunk-EVJ3DVH5.js";
14
6
  import {
15
7
  useTimeout
16
8
  } from "./chunk-YOJ6QPGS.js";
@@ -26,6 +18,14 @@ import {
26
18
  dist_exports5 as dist_exports3
27
19
  } from "./chunk-SGWD4VEU.js";
28
20
  import "./chunk-KFGKZMLK.js";
21
+ import {
22
+ Check_default,
23
+ ChevronDownVector_default,
24
+ DropdownVector_default,
25
+ Info_default,
26
+ Search_default,
27
+ X_default
28
+ } from "./chunk-XB525PXG.js";
29
29
  import {
30
30
  Input
31
31
  } from "./chunk-CQ3GYAYB.js";
@@ -7,112 +7,109 @@
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": "94bcffe9",
10
+ "fileHash": "709fc4d0",
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": "8fb77322",
16
+ "fileHash": "eafa73ff",
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": "ca004f1d",
22
+ "fileHash": "d42047ee",
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": "4f69b4ed",
28
+ "fileHash": "2da5ebc1",
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": "943d4cd6",
34
+ "fileHash": "d1e89e9a",
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": "9eeaf63b",
40
+ "fileHash": "2eb256ae",
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": "188194a5",
46
+ "fileHash": "999d1e76",
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": "bf451833",
52
+ "fileHash": "dbea2bc0",
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": "456dc53c",
58
+ "fileHash": "ab8d0314",
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": "8a137c67",
64
+ "fileHash": "eff0eed8",
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": "a5e61764",
70
+ "fileHash": "f422f1e9",
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": "21efb3b2",
76
+ "fileHash": "e7c6d178",
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": "68a7acff",
82
+ "fileHash": "5aa64b16",
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": "f2ad1539",
88
+ "fileHash": "c170b63c",
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": "90a4d0c8",
94
+ "fileHash": "784df595",
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": "02f8c5e8",
100
+ "fileHash": "4826be64",
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": "ace864a4",
106
+ "fileHash": "885aab86",
107
107
  "needsInterop": false
108
108
  }
109
109
  },
110
110
  "chunks": {
111
- "chunk-DQAZDQU3": {
112
- "file": "chunk-DQAZDQU3.js"
113
- },
114
- "chunk-XB525PXG": {
115
- "file": "chunk-XB525PXG.js"
111
+ "chunk-EVJ3DVH5": {
112
+ "file": "chunk-EVJ3DVH5.js"
116
113
  },
117
114
  "chunk-YOJ6QPGS": {
118
115
  "file": "chunk-YOJ6QPGS.js"
@@ -126,6 +123,9 @@
126
123
  "chunk-KFGKZMLK": {
127
124
  "file": "chunk-KFGKZMLK.js"
128
125
  },
126
+ "chunk-XB525PXG": {
127
+ "file": "chunk-XB525PXG.js"
128
+ },
129
129
  "chunk-CQ3GYAYB": {
130
130
  "file": "chunk-CQ3GYAYB.js"
131
131
  },
@@ -1,7 +1,3 @@
1
- import {
2
- Check_default,
3
- Copy_default
4
- } from "./chunk-XB525PXG.js";
5
1
  import {
6
2
  useTimeout
7
3
  } from "./chunk-YOJ6QPGS.js";
@@ -16,6 +12,10 @@ import {
16
12
  toTransformProperty,
17
13
  waitForAnimationFrame
18
14
  } from "./chunk-BAG6OO6S.js";
15
+ import {
16
+ Check_default,
17
+ Copy_default
18
+ } from "./chunk-XB525PXG.js";
19
19
  import {
20
20
  o
21
21
  } from "./chunk-QPJAV452.js";
@@ -625,4 +625,4 @@ export {
625
625
  ButtonLink,
626
626
  CopyButton
627
627
  };
628
- //# sourceMappingURL=chunk-DQAZDQU3.js.map
628
+ //# sourceMappingURL=chunk-EVJ3DVH5.js.map
@@ -1 +1 @@
1
- {"version":"4.0.13","results":[[":src/resources/counter-resource.test.tsx",{"duration":341.562457,"failed":false}],[":src/components/album/albums.test.tsx",{"duration":347.8453920000002,"failed":false}],[":src/resources/carousel-resource.test.tsx",{"duration":281.398232,"failed":false}],[":src/components/album/fullscreen-viewer.test.tsx",{"duration":245.63889299999983,"failed":false}],[":src/components/carousel/carousel.test.tsx",{"duration":64.06780200000003,"failed":false}],[":src/resources/albums-resource.test.tsx",{"duration":269.48677499999985,"failed":false}],[":src/components/album/film-strip.test.tsx",{"duration":490.057513,"failed":false}],[":src/components/album/album-card.test.tsx",{"duration":302.1487770000001,"failed":false}],[":src/components/card/card.test.tsx",{"duration":55.60354700000016,"failed":false}]]}
1
+ {"version":"4.0.13","results":[[":src/resources/carousel-resource.test.tsx",{"duration":272.55451200000016,"failed":false}],[":src/resources/counter-resource.test.tsx",{"duration":403.50324999999975,"failed":false}],[":src/components/album/albums.test.tsx",{"duration":332.906375,"failed":false}],[":src/components/album/fullscreen-viewer.test.tsx",{"duration":238.23859200000015,"failed":false}],[":src/components/album/album-carousel.test.tsx",{"duration":64.19374000000016,"failed":false}],[":src/components/carousel/carousel.test.tsx",{"duration":96.49479299999985,"failed":false}],[":src/resources/albums-resource.test.tsx",{"duration":257.8042250000001,"failed":false}],[":src/components/album/film-strip.test.tsx",{"duration":407.23125600000003,"failed":false}],[":src/components/album/album-card.test.tsx",{"duration":254.80172800000014,"failed":false}],[":src/components/carousel/card.test.tsx",{"duration":54.36003299999993,"failed":false}]]}
@@ -0,0 +1,84 @@
1
+ import { render } from '@testing-library/react';
2
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
3
+ import { AlbumCarousel } from './album-carousel';
4
+
5
+ const mockUseDisplayMode = vi.fn(() => 'inline');
6
+
7
+ // Mock sunpeak hooks
8
+ vi.mock('sunpeak', () => ({
9
+ useWidgetState: vi.fn(() => [{ currentIndex: 0 }, vi.fn()]),
10
+ useDisplayMode: () => mockUseDisplayMode(),
11
+ }));
12
+
13
+ // Mock embla-carousel-react
14
+ vi.mock('embla-carousel-react', () => ({
15
+ default: vi.fn(() => [vi.fn(), null]),
16
+ }));
17
+
18
+ // Mock embla-carousel-wheel-gestures
19
+ vi.mock('embla-carousel-wheel-gestures', () => ({
20
+ WheelGesturesPlugin: vi.fn(() => ({})),
21
+ }));
22
+
23
+ describe('AlbumCarousel', () => {
24
+ beforeEach(() => {
25
+ mockUseDisplayMode.mockReturnValue('inline');
26
+ });
27
+
28
+ it('renders all children with correct card width', () => {
29
+ const { container } = render(
30
+ <AlbumCarousel cardWidth={300}>
31
+ <div>Card 1</div>
32
+ <div>Card 2</div>
33
+ <div>Card 3</div>
34
+ </AlbumCarousel>
35
+ );
36
+
37
+ const cardContainers = container.querySelectorAll('.flex-none');
38
+ expect(cardContainers).toHaveLength(3);
39
+
40
+ cardContainers.forEach((cardContainer) => {
41
+ const element = cardContainer as HTMLElement;
42
+ expect(element.style.minWidth).toBe('300px');
43
+ expect(element.style.maxWidth).toBe('300px');
44
+ });
45
+ });
46
+
47
+ it('handles cardWidth object with inline/fullscreen modes', () => {
48
+ // Test inline mode
49
+ mockUseDisplayMode.mockReturnValue('inline');
50
+ const { container: inlineContainer } = render(
51
+ <AlbumCarousel cardWidth={{ inline: 250, fullscreen: 400 }}>
52
+ <div>Card 1</div>
53
+ </AlbumCarousel>
54
+ );
55
+
56
+ let cardContainer = inlineContainer.querySelector('.flex-none') as HTMLElement;
57
+ expect(cardContainer.style.minWidth).toBe('250px');
58
+
59
+ // Test fullscreen mode
60
+ mockUseDisplayMode.mockReturnValue('fullscreen');
61
+ const { container: fullscreenContainer } = render(
62
+ <AlbumCarousel cardWidth={{ inline: 250, fullscreen: 400 }}>
63
+ <div>Card 1</div>
64
+ </AlbumCarousel>
65
+ );
66
+
67
+ cardContainer = fullscreenContainer.querySelector('.flex-none') as HTMLElement;
68
+ expect(cardContainer.style.minWidth).toBe('400px');
69
+ });
70
+
71
+ it('applies custom gap between cards', () => {
72
+ const { container } = render(
73
+ <AlbumCarousel gap={24}>
74
+ <div>Card 1</div>
75
+ <div>Card 2</div>
76
+ </AlbumCarousel>
77
+ );
78
+
79
+ const carouselTrack = container.querySelector('.flex.touch-pan-y') as HTMLElement;
80
+ expect(carouselTrack.style.gap).toBe('24px');
81
+ expect(carouselTrack.style.marginLeft).toBe('-24px');
82
+ expect(carouselTrack.style.paddingLeft).toBe('24px');
83
+ });
84
+ });
@@ -0,0 +1,168 @@
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
+
9
+ export interface AlbumCarouselState extends Record<string, unknown> {
10
+ currentIndex?: number;
11
+ }
12
+
13
+ export type AlbumCarouselProps = {
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 AlbumCarousel = React.forwardRef<HTMLDivElement, AlbumCarouselProps>(
23
+ (
24
+ { children, gap = 16, showArrows = true, showEdgeGradients = true, cardWidth, className },
25
+ ref
26
+ ) => {
27
+ const [widgetState, setWidgetState] = useWidgetState<AlbumCarouselState>(() => ({
28
+ currentIndex: 0,
29
+ }));
30
+ const displayMode = useDisplayMode();
31
+
32
+ const [emblaRef, emblaApi] = useEmblaCarousel(
33
+ {
34
+ align: 'start',
35
+ dragFree: true,
36
+ containScroll: 'trimSnaps',
37
+ },
38
+ [WheelGesturesPlugin()]
39
+ );
40
+
41
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false);
42
+ const [canScrollNext, setCanScrollNext] = React.useState(false);
43
+
44
+ const scrollPrev = React.useCallback(() => {
45
+ if (emblaApi) emblaApi.scrollPrev();
46
+ }, [emblaApi]);
47
+
48
+ const scrollNext = React.useCallback(() => {
49
+ if (emblaApi) emblaApi.scrollNext();
50
+ }, [emblaApi]);
51
+
52
+ const onSelect = React.useCallback(() => {
53
+ if (!emblaApi) return;
54
+
55
+ setCanScrollPrev(emblaApi.canScrollPrev());
56
+ setCanScrollNext(emblaApi.canScrollNext());
57
+
58
+ const currentIndex = emblaApi.selectedScrollSnap();
59
+ if (widgetState?.currentIndex !== currentIndex) {
60
+ setWidgetState((prev) => ({ ...prev, currentIndex }));
61
+ }
62
+ }, [emblaApi, widgetState?.currentIndex, setWidgetState]);
63
+
64
+ React.useEffect(() => {
65
+ if (!emblaApi) return;
66
+
67
+ onSelect();
68
+ emblaApi.on('select', onSelect);
69
+ emblaApi.on('reInit', onSelect);
70
+
71
+ return () => {
72
+ emblaApi.off('select', onSelect);
73
+ emblaApi.off('reInit', onSelect);
74
+ };
75
+ }, [emblaApi, onSelect]);
76
+
77
+ const childArray = React.Children.toArray(children);
78
+
79
+ const getCardWidth = () => {
80
+ if (typeof cardWidth === 'number') {
81
+ return cardWidth;
82
+ }
83
+ if (cardWidth && typeof cardWidth === 'object') {
84
+ if (displayMode === 'fullscreen' && cardWidth.fullscreen) {
85
+ return cardWidth.fullscreen;
86
+ }
87
+ if (cardWidth.inline) {
88
+ return cardWidth.inline;
89
+ }
90
+ }
91
+ return 220;
92
+ };
93
+
94
+ const cardWidthPx = getCardWidth();
95
+
96
+ return (
97
+ <div ref={ref} className={cn('relative w-full', className)}>
98
+ {/* Left edge gradient */}
99
+ {showEdgeGradients && canScrollPrev && (
100
+ <div
101
+ className="pointer-events-none absolute left-0 top-0 z-10 h-full w-12 bg-gradient-to-r from-surface to-transparent"
102
+ aria-hidden="true"
103
+ />
104
+ )}
105
+
106
+ {/* Right edge gradient */}
107
+ {showEdgeGradients && canScrollNext && (
108
+ <div
109
+ className="pointer-events-none absolute right-0 top-0 z-10 h-full w-12 bg-gradient-to-l from-surface to-transparent"
110
+ aria-hidden="true"
111
+ />
112
+ )}
113
+
114
+ {/* Carousel viewport */}
115
+ <div ref={emblaRef} className="overflow-hidden w-full">
116
+ <div
117
+ className="flex touch-pan-y"
118
+ style={{
119
+ gap: `${gap}px`,
120
+ marginLeft: `-${gap}px`,
121
+ paddingLeft: `${gap}px`,
122
+ }}
123
+ >
124
+ {childArray.map((child, index) => (
125
+ <div
126
+ key={index}
127
+ className="flex-none"
128
+ style={{
129
+ minWidth: `${cardWidthPx}px`,
130
+ maxWidth: `${cardWidthPx}px`,
131
+ }}
132
+ >
133
+ {child}
134
+ </div>
135
+ ))}
136
+ </div>
137
+ </div>
138
+
139
+ {/* Previous button */}
140
+ {showArrows && canScrollPrev && (
141
+ <Button
142
+ variant="soft"
143
+ color="secondary"
144
+ onClick={scrollPrev}
145
+ className="absolute left-2 top-1/2 -translate-y-1/2 z-20 h-8 w-8 min-w-8 rounded-full p-0 shadow-md"
146
+ aria-label="Previous slide"
147
+ >
148
+ <ArrowLeft className="h-4 w-4" />
149
+ </Button>
150
+ )}
151
+
152
+ {/* Next button */}
153
+ {showArrows && canScrollNext && (
154
+ <Button
155
+ variant="soft"
156
+ color="secondary"
157
+ onClick={scrollNext}
158
+ className="absolute right-2 top-1/2 -translate-y-1/2 z-20 h-8 w-8 min-w-8 rounded-full p-0 shadow-md"
159
+ aria-label="Next slide"
160
+ >
161
+ <ArrowRight className="h-4 w-4" />
162
+ </Button>
163
+ )}
164
+ </div>
165
+ );
166
+ }
167
+ );
168
+ AlbumCarousel.displayName = 'AlbumCarousel';
@@ -29,8 +29,8 @@ vi.mock('./fullscreen-viewer', () => ({
29
29
  ),
30
30
  }));
31
31
 
32
- vi.mock('../carousel', () => ({
33
- Carousel: ({ children }: { children: React.ReactNode }) => (
32
+ vi.mock('./album-carousel', () => ({
33
+ AlbumCarousel: ({ children }: { children: React.ReactNode }) => (
34
34
  <div data-testid="carousel">{children}</div>
35
35
  ),
36
36
  }));
@@ -6,7 +6,7 @@ import {
6
6
  useWidgetProps,
7
7
  useUserAgent,
8
8
  } from 'sunpeak';
9
- import { Carousel } from '../carousel';
9
+ import { AlbumCarousel } from './album-carousel';
10
10
  import { AlbumCard } from './album-card';
11
11
  import { FullscreenViewer } from './fullscreen-viewer';
12
12
 
@@ -60,7 +60,7 @@ export const Albums = React.forwardRef<HTMLDivElement, AlbumsProps>(({ className
60
60
 
61
61
  return (
62
62
  <div ref={ref} className={className}>
63
- <Carousel gap={20} showArrows={false} showEdgeGradients={false} cardWidth={272}>
63
+ <AlbumCarousel gap={20} showArrows={false} showEdgeGradients={false} cardWidth={272}>
64
64
  {albums.map((album: Album) => (
65
65
  <AlbumCard
66
66
  key={album.id}
@@ -69,7 +69,7 @@ export const Albums = React.forwardRef<HTMLDivElement, AlbumsProps>(({ className
69
69
  buttonSize={hasTouch ? 'lg' : 'md'}
70
70
  />
71
71
  ))}
72
- </Carousel>
72
+ </AlbumCarousel>
73
73
  </div>
74
74
  );
75
75
  });
@@ -1,4 +1,5 @@
1
1
  export * from './album-card';
2
+ export * from './album-carousel';
2
3
  export * from './albums';
3
4
  export * from './fullscreen-viewer';
4
5
  export * from './film-strip';
@@ -1 +1,2 @@
1
1
  export * from './carousel';
2
+ export * from './card';
@@ -1,3 +1,2 @@
1
- export * from './card';
2
1
  export * from './carousel';
3
2
  export * from './album';
@@ -32,13 +32,10 @@ vi.mock('sunpeak', () => ({
32
32
  }));
33
33
 
34
34
  // Mock child components
35
- vi.mock('../components/carousel/carousel', () => ({
35
+ vi.mock('../components/carousel', () => ({
36
36
  Carousel: ({ children }: { children: React.ReactNode }) => (
37
37
  <div data-testid="carousel">{children}</div>
38
38
  ),
39
- }));
40
-
41
- vi.mock('../components/card/card', () => ({
42
39
  Card: ({ header, buttonSize }: { header: React.ReactNode; buttonSize?: string }) => (
43
40
  <div data-testid="card" data-button-size={buttonSize}>
44
41
  {header}
@@ -1,7 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { useWidgetProps, useSafeArea, useMaxHeight, useUserAgent } from 'sunpeak';
3
- import { Carousel } from '../components/carousel/carousel';
4
- import { Card } from '../components/card/card';
3
+ import { Carousel, Card } from '../components/carousel';
5
4
 
6
5
  /**
7
6
  * Production-ready Carousel Resource
@@ -1 +0,0 @@
1
- export * from './card';