sunpeak 0.1.25 → 0.2.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 (44) hide show
  1. package/README.md +9 -76
  2. package/bin/sunpeak.js +87 -0
  3. package/dist/index.cjs +827 -1361
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +98 -589
  6. package/dist/index.d.ts +98 -589
  7. package/dist/index.js +795 -1337
  8. package/dist/index.js.map +1 -1
  9. package/dist/styles/chatgpt/index.css +146 -0
  10. package/dist/styles/globals.css +220 -0
  11. package/package.json +34 -36
  12. package/template/.prettierignore +4 -0
  13. package/template/.prettierrc +9 -0
  14. package/template/README.md +47 -0
  15. package/template/assets/favicon.ico +0 -0
  16. package/template/components.json +21 -0
  17. package/template/dev/main.tsx +65 -0
  18. package/template/dev/styles.css +5 -0
  19. package/template/eslint.config.cjs +49 -0
  20. package/template/index.html +13 -0
  21. package/template/package.json +56 -0
  22. package/template/src/App.tsx +45 -0
  23. package/template/src/components/index.ts +2 -0
  24. package/template/src/components/shadcn/button.tsx +60 -0
  25. package/template/src/components/shadcn/card.tsx +76 -0
  26. package/template/src/components/shadcn/carousel.tsx +260 -0
  27. package/template/src/components/shadcn/index.ts +5 -0
  28. package/template/src/components/shadcn/label.tsx +24 -0
  29. package/template/src/components/shadcn/select.tsx +157 -0
  30. package/template/src/components/sunpeak-card.test.tsx +76 -0
  31. package/template/src/components/sunpeak-card.tsx +140 -0
  32. package/template/src/components/sunpeak-carousel.test.tsx +42 -0
  33. package/template/src/components/sunpeak-carousel.tsx +126 -0
  34. package/template/src/index.ts +3 -0
  35. package/template/src/lib/index.ts +1 -0
  36. package/template/src/lib/utils.ts +6 -0
  37. package/template/src/styles/chatgpt.css +146 -0
  38. package/template/src/styles/globals.css +220 -0
  39. package/template/src/test/setup.ts +37 -0
  40. package/template/tsconfig.json +32 -0
  41. package/template/tsconfig.node.json +11 -0
  42. package/template/tsup.config.ts +23 -0
  43. package/template/vite.config.ts +35 -0
  44. package/template/vitest.config.ts +15 -0
@@ -0,0 +1,60 @@
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/index"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
15
+ outline:
16
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost:
20
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
21
+ link: "text-primary underline-offset-4 hover:underline",
22
+ },
23
+ size: {
24
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
25
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
26
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
27
+ icon: "size-9",
28
+ "icon-sm": "size-8",
29
+ "icon-lg": "size-10",
30
+ },
31
+ },
32
+ defaultVariants: {
33
+ variant: "default",
34
+ size: "default",
35
+ },
36
+ }
37
+ )
38
+
39
+ function Button({
40
+ className,
41
+ variant,
42
+ size,
43
+ asChild = false,
44
+ ...props
45
+ }: React.ComponentProps<"button"> &
46
+ VariantProps<typeof buttonVariants> & {
47
+ asChild?: boolean
48
+ }) {
49
+ const Comp = asChild ? Slot : "button"
50
+
51
+ return (
52
+ <Comp
53
+ data-slot="button"
54
+ className={cn(buttonVariants({ variant, size, className }))}
55
+ {...props}
56
+ />
57
+ )
58
+ }
59
+
60
+ export { Button, buttonVariants }
@@ -0,0 +1,76 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/index"
4
+
5
+ const Card = React.forwardRef<
6
+ HTMLDivElement,
7
+ React.HTMLAttributes<HTMLDivElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div
10
+ ref={ref}
11
+ className={cn(
12
+ "rounded-xl border bg-card text-card-foreground shadow",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ ))
18
+ Card.displayName = "Card"
19
+
20
+ const CardHeader = React.forwardRef<
21
+ HTMLDivElement,
22
+ React.HTMLAttributes<HTMLDivElement>
23
+ >(({ className, ...props }, ref) => (
24
+ <div
25
+ ref={ref}
26
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
27
+ {...props}
28
+ />
29
+ ))
30
+ CardHeader.displayName = "CardHeader"
31
+
32
+ const CardTitle = React.forwardRef<
33
+ HTMLDivElement,
34
+ React.HTMLAttributes<HTMLDivElement>
35
+ >(({ className, ...props }, ref) => (
36
+ <div
37
+ ref={ref}
38
+ className={cn("font-semibold leading-none tracking-tight", className)}
39
+ {...props}
40
+ />
41
+ ))
42
+ CardTitle.displayName = "CardTitle"
43
+
44
+ const CardDescription = React.forwardRef<
45
+ HTMLDivElement,
46
+ React.HTMLAttributes<HTMLDivElement>
47
+ >(({ className, ...props }, ref) => (
48
+ <div
49
+ ref={ref}
50
+ className={cn("text-sm text-muted-foreground", className)}
51
+ {...props}
52
+ />
53
+ ))
54
+ CardDescription.displayName = "CardDescription"
55
+
56
+ const CardContent = React.forwardRef<
57
+ HTMLDivElement,
58
+ React.HTMLAttributes<HTMLDivElement>
59
+ >(({ className, ...props }, ref) => (
60
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
61
+ ))
62
+ CardContent.displayName = "CardContent"
63
+
64
+ const CardFooter = React.forwardRef<
65
+ HTMLDivElement,
66
+ React.HTMLAttributes<HTMLDivElement>
67
+ >(({ className, ...props }, ref) => (
68
+ <div
69
+ ref={ref}
70
+ className={cn("flex items-center p-6 pt-0", className)}
71
+ {...props}
72
+ />
73
+ ))
74
+ CardFooter.displayName = "CardFooter"
75
+
76
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
@@ -0,0 +1,260 @@
1
+ import * as React from "react"
2
+ import useEmblaCarousel, {
3
+ type UseEmblaCarouselType,
4
+ } from "embla-carousel-react"
5
+ import { ArrowLeft, ArrowRight } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/index"
8
+ import { Button } from "@/components/shadcn/button"
9
+
10
+ type CarouselApi = UseEmblaCarouselType[1]
11
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
12
+ type CarouselOptions = UseCarouselParameters[0]
13
+ type CarouselPlugin = UseCarouselParameters[1]
14
+
15
+ type CarouselProps = {
16
+ opts?: CarouselOptions
17
+ plugins?: CarouselPlugin
18
+ orientation?: "horizontal" | "vertical"
19
+ setApi?: (api: CarouselApi) => void
20
+ }
21
+
22
+ type CarouselContextProps = {
23
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0]
24
+ api: ReturnType<typeof useEmblaCarousel>[1]
25
+ scrollPrev: () => void
26
+ scrollNext: () => void
27
+ canScrollPrev: boolean
28
+ canScrollNext: boolean
29
+ } & CarouselProps
30
+
31
+ const CarouselContext = React.createContext<CarouselContextProps | null>(null)
32
+
33
+ function useCarousel() {
34
+ const context = React.useContext(CarouselContext)
35
+
36
+ if (!context) {
37
+ throw new Error("useCarousel must be used within a <Carousel />")
38
+ }
39
+
40
+ return context
41
+ }
42
+
43
+ const Carousel = React.forwardRef<
44
+ HTMLDivElement,
45
+ React.HTMLAttributes<HTMLDivElement> & CarouselProps
46
+ >(
47
+ (
48
+ {
49
+ orientation = "horizontal",
50
+ opts,
51
+ setApi,
52
+ plugins,
53
+ className,
54
+ children,
55
+ ...props
56
+ },
57
+ ref
58
+ ) => {
59
+ const [carouselRef, api] = useEmblaCarousel(
60
+ {
61
+ ...opts,
62
+ axis: orientation === "horizontal" ? "x" : "y",
63
+ },
64
+ plugins
65
+ )
66
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
67
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
68
+
69
+ const onSelect = React.useCallback((api: CarouselApi) => {
70
+ if (!api) {
71
+ return
72
+ }
73
+
74
+ setCanScrollPrev(api.canScrollPrev())
75
+ setCanScrollNext(api.canScrollNext())
76
+ }, [])
77
+
78
+ const scrollPrev = React.useCallback(() => {
79
+ api?.scrollPrev()
80
+ }, [api])
81
+
82
+ const scrollNext = React.useCallback(() => {
83
+ api?.scrollNext()
84
+ }, [api])
85
+
86
+ const handleKeyDown = React.useCallback(
87
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
88
+ if (event.key === "ArrowLeft") {
89
+ event.preventDefault()
90
+ scrollPrev()
91
+ } else if (event.key === "ArrowRight") {
92
+ event.preventDefault()
93
+ scrollNext()
94
+ }
95
+ },
96
+ [scrollPrev, scrollNext]
97
+ )
98
+
99
+ React.useEffect(() => {
100
+ if (!api || !setApi) {
101
+ return
102
+ }
103
+
104
+ setApi(api)
105
+ }, [api, setApi])
106
+
107
+ React.useEffect(() => {
108
+ if (!api) {
109
+ return
110
+ }
111
+
112
+ onSelect(api)
113
+ api.on("reInit", onSelect)
114
+ api.on("select", onSelect)
115
+
116
+ return () => {
117
+ api?.off("select", onSelect)
118
+ }
119
+ }, [api, onSelect])
120
+
121
+ return (
122
+ <CarouselContext.Provider
123
+ value={{
124
+ carouselRef,
125
+ api: api,
126
+ opts,
127
+ orientation:
128
+ orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
129
+ scrollPrev,
130
+ scrollNext,
131
+ canScrollPrev,
132
+ canScrollNext,
133
+ }}
134
+ >
135
+ <div
136
+ ref={ref}
137
+ onKeyDownCapture={handleKeyDown}
138
+ className={cn("relative", className)}
139
+ role="region"
140
+ aria-roledescription="carousel"
141
+ {...props}
142
+ >
143
+ {children}
144
+ </div>
145
+ </CarouselContext.Provider>
146
+ )
147
+ }
148
+ )
149
+ Carousel.displayName = "Carousel"
150
+
151
+ const CarouselContent = React.forwardRef<
152
+ HTMLDivElement,
153
+ React.HTMLAttributes<HTMLDivElement>
154
+ >(({ className, ...props }, ref) => {
155
+ const { carouselRef, orientation } = useCarousel()
156
+
157
+ return (
158
+ <div ref={carouselRef} className="overflow-hidden">
159
+ <div
160
+ ref={ref}
161
+ className={cn(
162
+ "flex",
163
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
164
+ className
165
+ )}
166
+ {...props}
167
+ />
168
+ </div>
169
+ )
170
+ })
171
+ CarouselContent.displayName = "CarouselContent"
172
+
173
+ const CarouselItem = React.forwardRef<
174
+ HTMLDivElement,
175
+ React.HTMLAttributes<HTMLDivElement>
176
+ >(({ className, ...props }, ref) => {
177
+ const { orientation } = useCarousel()
178
+
179
+ return (
180
+ <div
181
+ ref={ref}
182
+ role="group"
183
+ aria-roledescription="slide"
184
+ className={cn(
185
+ "min-w-0 shrink-0 grow-0 basis-full",
186
+ orientation === "horizontal" ? "pl-4" : "pt-4",
187
+ className
188
+ )}
189
+ {...props}
190
+ />
191
+ )
192
+ })
193
+ CarouselItem.displayName = "CarouselItem"
194
+
195
+ const CarouselPrevious = React.forwardRef<
196
+ HTMLButtonElement,
197
+ React.ComponentProps<typeof Button>
198
+ >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
199
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
200
+
201
+ return (
202
+ <Button
203
+ ref={ref}
204
+ variant={variant}
205
+ size={size}
206
+ className={cn(
207
+ "absolute h-8 w-8 rounded-full",
208
+ orientation === "horizontal"
209
+ ? "-left-12 top-1/2 -translate-y-1/2"
210
+ : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
211
+ className
212
+ )}
213
+ disabled={!canScrollPrev}
214
+ onClick={scrollPrev}
215
+ {...props}
216
+ >
217
+ <ArrowLeft className="h-4 w-4" />
218
+ <span className="sr-only">Previous slide</span>
219
+ </Button>
220
+ )
221
+ })
222
+ CarouselPrevious.displayName = "CarouselPrevious"
223
+
224
+ const CarouselNext = React.forwardRef<
225
+ HTMLButtonElement,
226
+ React.ComponentProps<typeof Button>
227
+ >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
228
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
229
+
230
+ return (
231
+ <Button
232
+ ref={ref}
233
+ variant={variant}
234
+ size={size}
235
+ className={cn(
236
+ "absolute h-8 w-8 rounded-full",
237
+ orientation === "horizontal"
238
+ ? "-right-12 top-1/2 -translate-y-1/2"
239
+ : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
240
+ className
241
+ )}
242
+ disabled={!canScrollNext}
243
+ onClick={scrollNext}
244
+ {...props}
245
+ >
246
+ <ArrowRight className="h-4 w-4" />
247
+ <span className="sr-only">Next slide</span>
248
+ </Button>
249
+ )
250
+ })
251
+ CarouselNext.displayName = "CarouselNext"
252
+
253
+ export {
254
+ type CarouselApi,
255
+ Carousel,
256
+ CarouselContent,
257
+ CarouselItem,
258
+ CarouselPrevious,
259
+ CarouselNext,
260
+ }
@@ -0,0 +1,5 @@
1
+ export * from './button';
2
+ export * from './card';
3
+ export * from './carousel';
4
+ export * from './label';
5
+ export * from './select';
@@ -0,0 +1,24 @@
1
+ import * as React from "react"
2
+ import * as LabelPrimitive from "@radix-ui/react-label"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/index"
6
+
7
+ const labelVariants = cva(
8
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
9
+ )
10
+
11
+ const Label = React.forwardRef<
12
+ React.ElementRef<typeof LabelPrimitive.Root>,
13
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
14
+ VariantProps<typeof labelVariants>
15
+ >(({ className, ...props }, ref) => (
16
+ <LabelPrimitive.Root
17
+ ref={ref}
18
+ className={cn(labelVariants(), className)}
19
+ {...props}
20
+ />
21
+ ))
22
+ Label.displayName = LabelPrimitive.Root.displayName
23
+
24
+ export { Label }
@@ -0,0 +1,157 @@
1
+ import * as React from "react"
2
+ import * as SelectPrimitive from "@radix-ui/react-select"
3
+ import { Check, ChevronDown, ChevronUp } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/index"
6
+
7
+ const Select = SelectPrimitive.Root
8
+
9
+ const SelectGroup = SelectPrimitive.Group
10
+
11
+ const SelectValue = SelectPrimitive.Value
12
+
13
+ const SelectTrigger = React.forwardRef<
14
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
15
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
16
+ >(({ className, children, ...props }, ref) => (
17
+ <SelectPrimitive.Trigger
18
+ ref={ref}
19
+ className={cn(
20
+ "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
21
+ className
22
+ )}
23
+ {...props}
24
+ >
25
+ {children}
26
+ <SelectPrimitive.Icon asChild>
27
+ <ChevronDown className="h-4 w-4 opacity-50" />
28
+ </SelectPrimitive.Icon>
29
+ </SelectPrimitive.Trigger>
30
+ ))
31
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
32
+
33
+ const SelectScrollUpButton = React.forwardRef<
34
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
35
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
36
+ >(({ className, ...props }, ref) => (
37
+ <SelectPrimitive.ScrollUpButton
38
+ ref={ref}
39
+ className={cn(
40
+ "flex cursor-default items-center justify-center py-1",
41
+ className
42
+ )}
43
+ {...props}
44
+ >
45
+ <ChevronUp className="h-4 w-4" />
46
+ </SelectPrimitive.ScrollUpButton>
47
+ ))
48
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
49
+
50
+ const SelectScrollDownButton = React.forwardRef<
51
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
52
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
53
+ >(({ className, ...props }, ref) => (
54
+ <SelectPrimitive.ScrollDownButton
55
+ ref={ref}
56
+ className={cn(
57
+ "flex cursor-default items-center justify-center py-1",
58
+ className
59
+ )}
60
+ {...props}
61
+ >
62
+ <ChevronDown className="h-4 w-4" />
63
+ </SelectPrimitive.ScrollDownButton>
64
+ ))
65
+ SelectScrollDownButton.displayName =
66
+ SelectPrimitive.ScrollDownButton.displayName
67
+
68
+ const SelectContent = React.forwardRef<
69
+ React.ElementRef<typeof SelectPrimitive.Content>,
70
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
71
+ >(({ className, children, position = "popper", ...props }, ref) => (
72
+ <SelectPrimitive.Portal>
73
+ <SelectPrimitive.Content
74
+ ref={ref}
75
+ className={cn(
76
+ "relative z-50 max-h-[--radix-select-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-select-content-transform-origin]",
77
+ position === "popper" &&
78
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
79
+ className
80
+ )}
81
+ position={position}
82
+ {...props}
83
+ >
84
+ <SelectScrollUpButton />
85
+ <SelectPrimitive.Viewport
86
+ className={cn(
87
+ "p-1",
88
+ position === "popper" &&
89
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
90
+ )}
91
+ >
92
+ {children}
93
+ </SelectPrimitive.Viewport>
94
+ <SelectScrollDownButton />
95
+ </SelectPrimitive.Content>
96
+ </SelectPrimitive.Portal>
97
+ ))
98
+ SelectContent.displayName = SelectPrimitive.Content.displayName
99
+
100
+ const SelectLabel = React.forwardRef<
101
+ React.ElementRef<typeof SelectPrimitive.Label>,
102
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
103
+ >(({ className, ...props }, ref) => (
104
+ <SelectPrimitive.Label
105
+ ref={ref}
106
+ className={cn("px-2 py-1.5 text-sm font-semibold", className)}
107
+ {...props}
108
+ />
109
+ ))
110
+ SelectLabel.displayName = SelectPrimitive.Label.displayName
111
+
112
+ const SelectItem = React.forwardRef<
113
+ React.ElementRef<typeof SelectPrimitive.Item>,
114
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
115
+ >(({ className, children, ...props }, ref) => (
116
+ <SelectPrimitive.Item
117
+ ref={ref}
118
+ className={cn(
119
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
120
+ className
121
+ )}
122
+ {...props}
123
+ >
124
+ <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
125
+ <SelectPrimitive.ItemIndicator>
126
+ <Check className="h-4 w-4" />
127
+ </SelectPrimitive.ItemIndicator>
128
+ </span>
129
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
130
+ </SelectPrimitive.Item>
131
+ ))
132
+ SelectItem.displayName = SelectPrimitive.Item.displayName
133
+
134
+ const SelectSeparator = React.forwardRef<
135
+ React.ElementRef<typeof SelectPrimitive.Separator>,
136
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
137
+ >(({ className, ...props }, ref) => (
138
+ <SelectPrimitive.Separator
139
+ ref={ref}
140
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
141
+ {...props}
142
+ />
143
+ ))
144
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName
145
+
146
+ export {
147
+ Select,
148
+ SelectGroup,
149
+ SelectValue,
150
+ SelectTrigger,
151
+ SelectContent,
152
+ SelectLabel,
153
+ SelectItem,
154
+ SelectSeparator,
155
+ SelectScrollUpButton,
156
+ SelectScrollDownButton,
157
+ }
@@ -0,0 +1,76 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { describe, it, expect, vi } from 'vitest';
4
+ import { SunpeakCard } from './sunpeak-card';
5
+
6
+ describe('SunpeakCard', () => {
7
+ it('renders image, header, metadata, and content', () => {
8
+ render(
9
+ <SunpeakCard
10
+ image="https://example.com/image.jpg"
11
+ imageAlt="Test image"
12
+ header="Test Header"
13
+ metadata="Test metadata"
14
+ >
15
+ Card content here
16
+ </SunpeakCard>
17
+ );
18
+
19
+ expect(screen.getByRole('img')).toHaveAttribute('src', 'https://example.com/image.jpg');
20
+ expect(screen.getByRole('img')).toHaveAttribute('alt', 'Test image');
21
+ expect(screen.getByText('Test Header')).toBeInTheDocument();
22
+ expect(screen.getByText('Test metadata')).toBeInTheDocument();
23
+ expect(screen.getByText('Card content here')).toBeInTheDocument();
24
+ });
25
+
26
+ it('applies variant styles correctly', () => {
27
+ const { rerender } = render(
28
+ <SunpeakCard
29
+ image="https://example.com/image.jpg"
30
+ imageAlt="Test"
31
+ variant="bordered"
32
+ data-testid="card"
33
+ />
34
+ );
35
+
36
+ expect(screen.getByTestId('card')).toHaveClass('border-2');
37
+
38
+ rerender(
39
+ <SunpeakCard
40
+ image="https://example.com/image.jpg"
41
+ imageAlt="Test"
42
+ variant="elevated"
43
+ data-testid="card"
44
+ />
45
+ );
46
+
47
+ expect(screen.getByTestId('card')).toHaveClass('shadow-lg');
48
+ });
49
+
50
+ it('renders buttons and handles clicks without propagating to card', async () => {
51
+ const user = userEvent.setup();
52
+ const cardClick = vi.fn();
53
+ const buttonClick = vi.fn();
54
+
55
+ render(
56
+ <SunpeakCard
57
+ image="https://example.com/image.jpg"
58
+ imageAlt="Test"
59
+ onClick={cardClick}
60
+ button1={{ children: 'Primary', onClick: buttonClick, isPrimary: true }}
61
+ button2={{ children: 'Secondary', onClick: buttonClick }}
62
+ />
63
+ );
64
+
65
+ const primaryButton = screen.getByRole('button', { name: 'Primary' });
66
+ const secondaryButton = screen.getByRole('button', { name: 'Secondary' });
67
+
68
+ expect(primaryButton).toBeInTheDocument();
69
+ expect(secondaryButton).toBeInTheDocument();
70
+
71
+ await user.click(primaryButton);
72
+
73
+ expect(buttonClick).toHaveBeenCalledTimes(1);
74
+ expect(cardClick).not.toHaveBeenCalled();
75
+ });
76
+ });