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.
- package/README.md +9 -76
- package/bin/sunpeak.js +87 -0
- package/dist/index.cjs +827 -1361
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +98 -589
- package/dist/index.d.ts +98 -589
- package/dist/index.js +795 -1337
- package/dist/index.js.map +1 -1
- package/dist/styles/chatgpt/index.css +146 -0
- package/dist/styles/globals.css +220 -0
- package/package.json +34 -36
- package/template/.prettierignore +4 -0
- package/template/.prettierrc +9 -0
- package/template/README.md +47 -0
- package/template/assets/favicon.ico +0 -0
- package/template/components.json +21 -0
- package/template/dev/main.tsx +65 -0
- package/template/dev/styles.css +5 -0
- package/template/eslint.config.cjs +49 -0
- package/template/index.html +13 -0
- package/template/package.json +56 -0
- package/template/src/App.tsx +45 -0
- package/template/src/components/index.ts +2 -0
- package/template/src/components/shadcn/button.tsx +60 -0
- package/template/src/components/shadcn/card.tsx +76 -0
- package/template/src/components/shadcn/carousel.tsx +260 -0
- package/template/src/components/shadcn/index.ts +5 -0
- package/template/src/components/shadcn/label.tsx +24 -0
- package/template/src/components/shadcn/select.tsx +157 -0
- package/template/src/components/sunpeak-card.test.tsx +76 -0
- package/template/src/components/sunpeak-card.tsx +140 -0
- package/template/src/components/sunpeak-carousel.test.tsx +42 -0
- package/template/src/components/sunpeak-carousel.tsx +126 -0
- package/template/src/index.ts +3 -0
- package/template/src/lib/index.ts +1 -0
- package/template/src/lib/utils.ts +6 -0
- package/template/src/styles/chatgpt.css +146 -0
- package/template/src/styles/globals.css +220 -0
- package/template/src/test/setup.ts +37 -0
- package/template/tsconfig.json +32 -0
- package/template/tsconfig.node.json +11 -0
- package/template/tsup.config.ts +23 -0
- package/template/vite.config.ts +35 -0
- package/template/vitest.config.ts +15 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "@/lib/index"
|
|
3
|
+
import {
|
|
4
|
+
Card,
|
|
5
|
+
CardContent,
|
|
6
|
+
CardDescription,
|
|
7
|
+
CardFooter,
|
|
8
|
+
CardHeader,
|
|
9
|
+
CardTitle,
|
|
10
|
+
} from "@/components/shadcn/card"
|
|
11
|
+
import { Button } from "@/components/shadcn/button"
|
|
12
|
+
|
|
13
|
+
export interface SunpeakButtonProps extends Omit<React.ComponentProps<typeof Button>, 'onClick'> {
|
|
14
|
+
isPrimary?: boolean
|
|
15
|
+
onClick: () => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SunpeakCardProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
19
|
+
children?: React.ReactNode
|
|
20
|
+
image: string
|
|
21
|
+
imageAlt: string
|
|
22
|
+
imageMaxWidth?: number
|
|
23
|
+
imageMaxHeight?: number
|
|
24
|
+
header?: React.ReactNode
|
|
25
|
+
metadata?: React.ReactNode
|
|
26
|
+
button1?: SunpeakButtonProps
|
|
27
|
+
button2?: SunpeakButtonProps
|
|
28
|
+
variant?: "default" | "bordered" | "elevated"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const SunpeakCard = React.forwardRef<HTMLDivElement, SunpeakCardProps>(
|
|
32
|
+
(
|
|
33
|
+
{
|
|
34
|
+
children,
|
|
35
|
+
image,
|
|
36
|
+
imageAlt,
|
|
37
|
+
imageMaxWidth = 400,
|
|
38
|
+
imageMaxHeight = 400,
|
|
39
|
+
header,
|
|
40
|
+
metadata,
|
|
41
|
+
button1,
|
|
42
|
+
button2,
|
|
43
|
+
variant = "default",
|
|
44
|
+
className,
|
|
45
|
+
onClick,
|
|
46
|
+
...props
|
|
47
|
+
},
|
|
48
|
+
ref
|
|
49
|
+
) => {
|
|
50
|
+
const hasButtons = button1 || button2
|
|
51
|
+
|
|
52
|
+
const handleCardClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
53
|
+
onClick?.(e)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const renderButton = (buttonProps: SunpeakButtonProps) => {
|
|
57
|
+
const { isPrimary = false, onClick: buttonOnClick, children, ...restProps } = buttonProps
|
|
58
|
+
|
|
59
|
+
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
60
|
+
e.stopPropagation()
|
|
61
|
+
buttonOnClick()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Button
|
|
66
|
+
{...restProps}
|
|
67
|
+
variant={isPrimary ? "default" : "outline"}
|
|
68
|
+
onClick={handleClick}
|
|
69
|
+
>
|
|
70
|
+
{children}
|
|
71
|
+
</Button>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const variantClasses = {
|
|
76
|
+
default: "",
|
|
77
|
+
bordered: "border-2",
|
|
78
|
+
elevated: "shadow-lg",
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Card
|
|
83
|
+
ref={ref}
|
|
84
|
+
className={cn(
|
|
85
|
+
"overflow-hidden cursor-pointer select-none",
|
|
86
|
+
variantClasses[variant],
|
|
87
|
+
className
|
|
88
|
+
)}
|
|
89
|
+
onClick={handleCardClick}
|
|
90
|
+
{...props}
|
|
91
|
+
>
|
|
92
|
+
{image && (
|
|
93
|
+
<div className="w-full overflow-hidden">
|
|
94
|
+
<img
|
|
95
|
+
src={image}
|
|
96
|
+
alt={imageAlt}
|
|
97
|
+
loading="lazy"
|
|
98
|
+
className="w-full h-auto aspect-square object-cover"
|
|
99
|
+
style={{
|
|
100
|
+
maxWidth: `${imageMaxWidth}px`,
|
|
101
|
+
maxHeight: `${imageMaxHeight}px`,
|
|
102
|
+
}}
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
106
|
+
<div className="flex flex-col flex-1">
|
|
107
|
+
{(header || metadata) && (
|
|
108
|
+
<CardHeader className={cn(image && "pt-3")}>
|
|
109
|
+
{header && (
|
|
110
|
+
<CardTitle className="text-base font-medium leading-tight overflow-hidden text-ellipsis whitespace-nowrap">
|
|
111
|
+
{header}
|
|
112
|
+
</CardTitle>
|
|
113
|
+
)}
|
|
114
|
+
{metadata && (
|
|
115
|
+
<CardDescription className="text-xs leading-normal">
|
|
116
|
+
{metadata}
|
|
117
|
+
</CardDescription>
|
|
118
|
+
)}
|
|
119
|
+
</CardHeader>
|
|
120
|
+
)}
|
|
121
|
+
{children && (
|
|
122
|
+
<CardContent className={cn(
|
|
123
|
+
"text-sm leading-normal",
|
|
124
|
+
(header || metadata) && "pt-1"
|
|
125
|
+
)}>
|
|
126
|
+
<div className="line-clamp-2">{children}</div>
|
|
127
|
+
</CardContent>
|
|
128
|
+
)}
|
|
129
|
+
{hasButtons && (
|
|
130
|
+
<CardFooter className="flex gap-2 flex-wrap">
|
|
131
|
+
{button1 && renderButton(button1)}
|
|
132
|
+
{button2 && renderButton(button2)}
|
|
133
|
+
</CardFooter>
|
|
134
|
+
)}
|
|
135
|
+
</div>
|
|
136
|
+
</Card>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
SunpeakCard.displayName = "SunpeakCard"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { SunpeakCarousel } from './sunpeak-carousel';
|
|
4
|
+
|
|
5
|
+
describe('SunpeakCarousel', () => {
|
|
6
|
+
it('renders all children as carousel items', () => {
|
|
7
|
+
render(
|
|
8
|
+
<SunpeakCarousel>
|
|
9
|
+
<div>Item 1</div>
|
|
10
|
+
<div>Item 2</div>
|
|
11
|
+
<div>Item 3</div>
|
|
12
|
+
</SunpeakCarousel>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
expect(screen.getByText('Item 1')).toBeInTheDocument();
|
|
16
|
+
expect(screen.getByText('Item 2')).toBeInTheDocument();
|
|
17
|
+
expect(screen.getByText('Item 3')).toBeInTheDocument();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('applies custom gap spacing to carousel content', () => {
|
|
21
|
+
render(
|
|
22
|
+
<SunpeakCarousel gap={24} data-testid="carousel">
|
|
23
|
+
<div>Item 1</div>
|
|
24
|
+
<div>Item 2</div>
|
|
25
|
+
</SunpeakCarousel>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const carouselContent = document.querySelector('[class*="ml-0"]');
|
|
29
|
+
expect(carouselContent).toHaveStyle({ gap: '24px' });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('applies custom card width to carousel items', () => {
|
|
33
|
+
render(
|
|
34
|
+
<SunpeakCarousel cardWidth={300}>
|
|
35
|
+
<div>Item 1</div>
|
|
36
|
+
</SunpeakCarousel>
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const carouselItem = document.querySelector('[class*="pl-0"]');
|
|
40
|
+
expect(carouselItem).toHaveStyle({ flexBasis: '300px', minWidth: '300px' });
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { WheelGesturesPlugin } from "embla-carousel-wheel-gestures"
|
|
3
|
+
import { cn } from "@/lib/index"
|
|
4
|
+
import {
|
|
5
|
+
Carousel,
|
|
6
|
+
CarouselContent,
|
|
7
|
+
CarouselItem,
|
|
8
|
+
CarouselNext,
|
|
9
|
+
CarouselPrevious,
|
|
10
|
+
type CarouselApi,
|
|
11
|
+
} from "@/components/shadcn/carousel"
|
|
12
|
+
|
|
13
|
+
export type SunpeakCarouselProps = {
|
|
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 SunpeakCarousel = React.forwardRef<
|
|
23
|
+
HTMLDivElement,
|
|
24
|
+
SunpeakCarouselProps
|
|
25
|
+
>(
|
|
26
|
+
(
|
|
27
|
+
{
|
|
28
|
+
children,
|
|
29
|
+
gap = 16,
|
|
30
|
+
showArrows = true,
|
|
31
|
+
showEdgeGradients = true,
|
|
32
|
+
cardWidth,
|
|
33
|
+
className,
|
|
34
|
+
},
|
|
35
|
+
ref
|
|
36
|
+
) => {
|
|
37
|
+
const [api, setApi] = React.useState<CarouselApi>()
|
|
38
|
+
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
|
|
39
|
+
const [canScrollNext, setCanScrollNext] = React.useState(false)
|
|
40
|
+
|
|
41
|
+
React.useEffect(() => {
|
|
42
|
+
if (!api) return
|
|
43
|
+
|
|
44
|
+
const onSelect = () => {
|
|
45
|
+
setCanScrollPrev(api.canScrollPrev())
|
|
46
|
+
setCanScrollNext(api.canScrollNext())
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
onSelect()
|
|
50
|
+
api.on("select", onSelect)
|
|
51
|
+
api.on("reInit", onSelect)
|
|
52
|
+
|
|
53
|
+
return () => {
|
|
54
|
+
api.off("select", onSelect)
|
|
55
|
+
api.off("reInit", onSelect)
|
|
56
|
+
}
|
|
57
|
+
}, [api])
|
|
58
|
+
|
|
59
|
+
const childArray = React.Children.toArray(children)
|
|
60
|
+
|
|
61
|
+
const getCardWidth = () => {
|
|
62
|
+
if (typeof cardWidth === "number") {
|
|
63
|
+
return `${cardWidth}px`
|
|
64
|
+
}
|
|
65
|
+
if (cardWidth?.inline) {
|
|
66
|
+
return `${cardWidth.inline}px`
|
|
67
|
+
}
|
|
68
|
+
return "220px"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div ref={ref} className={cn("relative w-full", className)}>
|
|
73
|
+
{showEdgeGradients && canScrollPrev && (
|
|
74
|
+
<div
|
|
75
|
+
className="pointer-events-none absolute left-0 top-0 z-10 h-full w-12 bg-gradient-to-r from-background to-transparent transition-opacity"
|
|
76
|
+
aria-hidden="true"
|
|
77
|
+
/>
|
|
78
|
+
)}
|
|
79
|
+
{showEdgeGradients && canScrollNext && (
|
|
80
|
+
<div
|
|
81
|
+
className="pointer-events-none absolute right-0 top-0 z-10 h-full w-12 bg-gradient-to-l from-background to-transparent transition-opacity"
|
|
82
|
+
aria-hidden="true"
|
|
83
|
+
/>
|
|
84
|
+
)}
|
|
85
|
+
|
|
86
|
+
<Carousel
|
|
87
|
+
opts={{
|
|
88
|
+
align: "start",
|
|
89
|
+
dragFree: true,
|
|
90
|
+
}}
|
|
91
|
+
plugins={[WheelGesturesPlugin()]}
|
|
92
|
+
setApi={setApi}
|
|
93
|
+
className="w-full"
|
|
94
|
+
>
|
|
95
|
+
<CarouselContent
|
|
96
|
+
className="ml-0"
|
|
97
|
+
style={{
|
|
98
|
+
gap: `${gap}px`,
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
{childArray.map((child, index) => (
|
|
102
|
+
<CarouselItem
|
|
103
|
+
key={index}
|
|
104
|
+
className="pl-0"
|
|
105
|
+
style={{
|
|
106
|
+
flexBasis: getCardWidth(),
|
|
107
|
+
minWidth: getCardWidth(),
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
{child}
|
|
111
|
+
</CarouselItem>
|
|
112
|
+
))}
|
|
113
|
+
</CarouselContent>
|
|
114
|
+
|
|
115
|
+
{showArrows && canScrollPrev && (
|
|
116
|
+
<CarouselPrevious className="left-2" />
|
|
117
|
+
)}
|
|
118
|
+
{showArrows && canScrollNext && (
|
|
119
|
+
<CarouselNext className="right-2" />
|
|
120
|
+
)}
|
|
121
|
+
</Carousel>
|
|
122
|
+
</div>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
SunpeakCarousel.displayName = "SunpeakCarousel"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { cn } from './utils';
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI ChatGPT Apps SDK Design System
|
|
3
|
+
*
|
|
4
|
+
* Implements Sunpeak base variables according to OpenAI ChatGPT Apps SDK design guidelines.
|
|
5
|
+
* https://developers.openai.com/apps-sdk/concepts/design-guidelines
|
|
6
|
+
*
|
|
7
|
+
* Design Principles:
|
|
8
|
+
* - "Always inherit the system font stack"
|
|
9
|
+
* - "Use system colors for text, icons, and spatial elements"
|
|
10
|
+
* - "Use system grid spacing"
|
|
11
|
+
* - "Respect system specified corner rounds"
|
|
12
|
+
*
|
|
13
|
+
* ⚠️ These values should NOT be overridden unless you want to break
|
|
14
|
+
* compliance with OpenAI ChatGPT Apps SDK design guidelines.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
:root {
|
|
18
|
+
/* ===================================
|
|
19
|
+
* TYPOGRAPHY - System Fonts
|
|
20
|
+
* ===================================
|
|
21
|
+
* "Always inherit the system font stack"
|
|
22
|
+
* Platform-native: SF Pro (iOS/macOS), Roboto (Android), Segoe UI (Windows)
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
--sp-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
26
|
+
|
|
27
|
+
/* Font sizes - System-defined scale */
|
|
28
|
+
--sp-font-size-xs: 0.75rem; /* 12px - metadata */
|
|
29
|
+
--sp-font-size-sm: 0.875rem; /* 14px - descriptions */
|
|
30
|
+
--sp-font-size-base: 1rem; /* 16px - headers */
|
|
31
|
+
--sp-font-size-lg: 1.125rem; /* 18px */
|
|
32
|
+
--sp-font-size-xl: 1.25rem; /* 20px */
|
|
33
|
+
|
|
34
|
+
/* Font weights - System-defined */
|
|
35
|
+
--sp-font-weight-normal: 400;
|
|
36
|
+
--sp-font-weight-medium: 500;
|
|
37
|
+
--sp-font-weight-semibold: 600;
|
|
38
|
+
--sp-font-weight-bold: 700;
|
|
39
|
+
|
|
40
|
+
/* Line heights - System-defined */
|
|
41
|
+
--sp-line-height-tight: 1.25;
|
|
42
|
+
--sp-line-height-normal: 1.5;
|
|
43
|
+
--sp-line-height-relaxed: 1.75;
|
|
44
|
+
|
|
45
|
+
/* ===================================
|
|
46
|
+
* SYSTEM COLORS
|
|
47
|
+
* ===================================
|
|
48
|
+
* "Use system colors for text, icons, and spatial elements"
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
/* Light Mode - OpenAI ChatGPT System Colors */
|
|
52
|
+
--sp-light-color-bg-primary: #ffffff;
|
|
53
|
+
--sp-light-color-bg-secondary: #e8e8e8;
|
|
54
|
+
--sp-light-color-bg-tertiary: #f3f3f3;
|
|
55
|
+
--sp-light-color-text-primary: #0d0d0d;
|
|
56
|
+
--sp-light-color-text-secondary: #5d5d5d;
|
|
57
|
+
--sp-light-color-text-tertiary: #8f8f8f;
|
|
58
|
+
--sp-light-color-text-inverted: #8f8f8f;
|
|
59
|
+
--sp-light-color-border: rgba(0, 0, 0, 0.05);
|
|
60
|
+
--sp-light-success: #008635;
|
|
61
|
+
--sp-light-warning: #e25507;
|
|
62
|
+
--sp-light-error: #e02e2a;
|
|
63
|
+
--sp-light-info: #0285ff;
|
|
64
|
+
--sp-light-sidebar: #f9f9f9;
|
|
65
|
+
/* Customizable light styles */
|
|
66
|
+
--sp-light-accent: #f46c21;
|
|
67
|
+
--sp-light-accent-hover: rgba(244, 108, 33, 0.9);
|
|
68
|
+
--sp-light-accent-active: #d45e1c;
|
|
69
|
+
--sp-light-accent-foreground: #ffffff;
|
|
70
|
+
|
|
71
|
+
/* Dark Mode - OpenAI ChatGPT System Colors */
|
|
72
|
+
--sp-dark-color-bg-primary: #212121;
|
|
73
|
+
--sp-dark-color-bg-secondary: #303030;
|
|
74
|
+
--sp-dark-color-bg-tertiary: #414141;
|
|
75
|
+
--sp-dark-color-text-primary: #ffffff;
|
|
76
|
+
--sp-dark-color-text-secondary: #cdcdcd;
|
|
77
|
+
--sp-dark-color-text-tertiary: #afafaf;
|
|
78
|
+
--sp-dark-color-text-inverted: #afafaf;
|
|
79
|
+
--sp-dark-color-border: #ffffff0d;
|
|
80
|
+
--sp-dark-success: #40c977;
|
|
81
|
+
--sp-dark-warning: #ff9e6c;
|
|
82
|
+
--sp-dark-error: #ff8583;
|
|
83
|
+
--sp-dark-info: #0285ff;
|
|
84
|
+
--sp-dark-sidebar: #181818;
|
|
85
|
+
/* Customizable dark styles */
|
|
86
|
+
--sp-dark-accent: #f46c21;
|
|
87
|
+
--sp-dark-accent-hover: rgba(244, 108, 33, 0.9);
|
|
88
|
+
--sp-dark-accent-active: #d45e1c;
|
|
89
|
+
--sp-dark-accent-foreground: #ffffff;
|
|
90
|
+
|
|
91
|
+
/* ===================================
|
|
92
|
+
* SPACING - System Grid
|
|
93
|
+
* ===================================
|
|
94
|
+
* "Use system grid spacing"
|
|
95
|
+
* 4px base grid system
|
|
96
|
+
*/
|
|
97
|
+
|
|
98
|
+
--sp-spacing-1: 0.25rem; /* 4px */
|
|
99
|
+
--sp-spacing-2: 0.5rem; /* 8px */
|
|
100
|
+
--sp-spacing-3: 0.75rem; /* 12px */
|
|
101
|
+
--sp-spacing-4: 1rem; /* 16px */
|
|
102
|
+
--sp-spacing-5: 1.25rem; /* 20px */
|
|
103
|
+
--sp-spacing-6: 1.5rem; /* 24px */
|
|
104
|
+
--sp-spacing-8: 2rem; /* 32px */
|
|
105
|
+
|
|
106
|
+
/* ===================================
|
|
107
|
+
* BORDER RADIUS
|
|
108
|
+
* ===================================
|
|
109
|
+
* "Respect system specified corner rounds"
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
--sp-radius-sm: 0.375rem; /* 6px */
|
|
113
|
+
--sp-radius-md: 0.5rem; /* 8px */
|
|
114
|
+
--sp-radius-lg: 0.75rem; /* 12px */
|
|
115
|
+
--sp-radius-xl: 1rem; /* 16px */
|
|
116
|
+
--sp-radius-2xl: 1.5rem; /* 24px */
|
|
117
|
+
--sp-radius-full: 9999px;
|
|
118
|
+
|
|
119
|
+
/* ===================================
|
|
120
|
+
* SHADOWS - System Elevation
|
|
121
|
+
* ===================================
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
--sp-shadow-sm: 0px 1px 2px rgba(0, 0, 0, 0.05);
|
|
125
|
+
--sp-shadow-md: 0px 2px 6px rgba(0, 0, 0, 0.06);
|
|
126
|
+
--sp-shadow-lg: 0px 4px 12px rgba(0, 0, 0, 0.1);
|
|
127
|
+
--sp-shadow-xl: 0px 8px 24px rgba(0, 0, 0, 0.12);
|
|
128
|
+
|
|
129
|
+
/* ===================================
|
|
130
|
+
* COMPONENT DIMENSIONS
|
|
131
|
+
* ===================================
|
|
132
|
+
* Standard component sizing from OpenAI ChatGPT examples
|
|
133
|
+
*/
|
|
134
|
+
|
|
135
|
+
/* Card width - 220px (OpenAI ChatGPT standard) */
|
|
136
|
+
--sp-card-width: 220px;
|
|
137
|
+
|
|
138
|
+
/* Card gap - 16px (OpenAI ChatGPT standard) */
|
|
139
|
+
--sp-card-gap: 1rem;
|
|
140
|
+
|
|
141
|
+
/* Button padding */
|
|
142
|
+
--sp-button-padding-x: 1rem; /* 16px */
|
|
143
|
+
--sp-button-padding-y: 0.375rem; /* 6px */
|
|
144
|
+
--sp-button-padding-x-sm: 0.75rem; /* 12px */
|
|
145
|
+
--sp-button-padding-y-sm: 0.25rem; /* 4px */
|
|
146
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
|
|
4
|
+
@custom-variant dark (&:is(.dark *));
|
|
5
|
+
|
|
6
|
+
/* Tailwind v4 theme configuration */
|
|
7
|
+
@theme {
|
|
8
|
+
/* ===================================
|
|
9
|
+
* COLORS - Mapped to shadcn and --sp- variables
|
|
10
|
+
* ===================================
|
|
11
|
+
*/
|
|
12
|
+
--color-*: initial;
|
|
13
|
+
|
|
14
|
+
/* Shadcn colors */
|
|
15
|
+
--color-background: var(--background);
|
|
16
|
+
--color-foreground: var(--foreground);
|
|
17
|
+
--color-border: var(--border);
|
|
18
|
+
--color-card: var(--card);
|
|
19
|
+
--color-card-foreground: var(--card-foreground);
|
|
20
|
+
--color-popover: var(--popover);
|
|
21
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
22
|
+
--color-primary: var(--primary);
|
|
23
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
24
|
+
--color-secondary: var(--secondary);
|
|
25
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
26
|
+
--color-muted: var(--muted);
|
|
27
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
28
|
+
--color-accent: var(--accent);
|
|
29
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
30
|
+
--color-destructive: var(--destructive);
|
|
31
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
32
|
+
--color-input: var(--input);
|
|
33
|
+
--color-ring: var(--ring);
|
|
34
|
+
|
|
35
|
+
/* Sunpeak semantic colors */
|
|
36
|
+
--color-success: var(--sp-success);
|
|
37
|
+
--color-warning: var(--sp-warning);
|
|
38
|
+
--color-error: var(--sp-error);
|
|
39
|
+
--color-info: var(--sp-info);
|
|
40
|
+
|
|
41
|
+
/* ===================================
|
|
42
|
+
* TYPOGRAPHY - Mapped to --sp- variables
|
|
43
|
+
* ===================================
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/* Font families */
|
|
47
|
+
--font-sans: var(--sp-font-family);
|
|
48
|
+
|
|
49
|
+
/* Font sizes */
|
|
50
|
+
--text-xs: var(--sp-font-size-xs);
|
|
51
|
+
--text-sm: var(--sp-font-size-sm);
|
|
52
|
+
--text-base: var(--sp-font-size-base);
|
|
53
|
+
--text-lg: var(--sp-font-size-lg);
|
|
54
|
+
--text-xl: var(--sp-font-size-xl);
|
|
55
|
+
|
|
56
|
+
/* Font weights */
|
|
57
|
+
--font-weight-normal: var(--sp-font-weight-normal);
|
|
58
|
+
--font-weight-medium: var(--sp-font-weight-medium);
|
|
59
|
+
--font-weight-semibold: var(--sp-font-weight-semibold);
|
|
60
|
+
--font-weight-bold: var(--sp-font-weight-bold);
|
|
61
|
+
|
|
62
|
+
/* Line heights */
|
|
63
|
+
--leading-tight: var(--sp-line-height-tight);
|
|
64
|
+
--leading-normal: var(--sp-line-height-normal);
|
|
65
|
+
--leading-relaxed: var(--sp-line-height-relaxed);
|
|
66
|
+
|
|
67
|
+
/* ===================================
|
|
68
|
+
* SPACING - Mapped to --sp- variables
|
|
69
|
+
* ===================================
|
|
70
|
+
*/
|
|
71
|
+
--spacing-1: var(--sp-spacing-1);
|
|
72
|
+
--spacing-2: var(--sp-spacing-2);
|
|
73
|
+
--spacing-3: var(--sp-spacing-3);
|
|
74
|
+
--spacing-4: var(--sp-spacing-4);
|
|
75
|
+
--spacing-5: var(--sp-spacing-5);
|
|
76
|
+
--spacing-6: var(--sp-spacing-6);
|
|
77
|
+
--spacing-8: var(--sp-spacing-8);
|
|
78
|
+
|
|
79
|
+
/* ===================================
|
|
80
|
+
* BORDER RADIUS - Mapped to --sp- variables
|
|
81
|
+
* ===================================
|
|
82
|
+
*/
|
|
83
|
+
--radius-sm: var(--sp-radius-sm);
|
|
84
|
+
--radius-md: var(--sp-radius-md);
|
|
85
|
+
--radius-lg: var(--sp-radius-lg);
|
|
86
|
+
--radius-xl: var(--sp-radius-xl);
|
|
87
|
+
--radius-2xl: var(--sp-radius-2xl);
|
|
88
|
+
--radius-full: var(--sp-radius-full);
|
|
89
|
+
|
|
90
|
+
/* ===================================
|
|
91
|
+
* SHADOWS - Mapped to --sp- variables
|
|
92
|
+
* ===================================
|
|
93
|
+
*/
|
|
94
|
+
--shadow-sm: var(--sp-shadow-sm);
|
|
95
|
+
--shadow-md: var(--sp-shadow-md);
|
|
96
|
+
--shadow-lg: var(--sp-shadow-lg);
|
|
97
|
+
--shadow-xl: var(--sp-shadow-xl);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* ===================================
|
|
101
|
+
* THEME MODE CLASSES
|
|
102
|
+
* ===================================
|
|
103
|
+
* Force light/dark mode on specific elements
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
.light {
|
|
107
|
+
--sp-color-bg-primary: var(--sp-light-color-bg-primary);
|
|
108
|
+
--sp-color-bg-secondary: var(--sp-light-color-bg-secondary);
|
|
109
|
+
--sp-color-bg-tertiary: var(--sp-light-color-bg-tertiary);
|
|
110
|
+
--sp-color-text-primary: var(--sp-light-color-text-primary);
|
|
111
|
+
--sp-color-text-secondary: var(--sp-light-color-text-secondary);
|
|
112
|
+
--sp-color-text-tertiary: var(--sp-light-color-text-tertiary);
|
|
113
|
+
--sp-color-text-inverted: var(--sp-light-color-text-inverted);
|
|
114
|
+
--sp-color-border: var(--sp-light-color-border);
|
|
115
|
+
--sp-success: var(--sp-light-success);
|
|
116
|
+
--sp-warning: var(--sp-light-warning);
|
|
117
|
+
--sp-error: var(--sp-light-error);
|
|
118
|
+
--sp-info: var(--sp-light-info);
|
|
119
|
+
--sp-accent: var(--sp-light-accent);
|
|
120
|
+
--sp-accent-hover: var(--sp-light-accent-hover);
|
|
121
|
+
--sp-accent-active: var(--sp-light-accent-active);
|
|
122
|
+
--sp-accent-foreground: var(--sp-light-accent-foreground);
|
|
123
|
+
|
|
124
|
+
/* shadcn/ui light mode variables */
|
|
125
|
+
--background: var(--sp-light-color-bg-primary);
|
|
126
|
+
--foreground: var(--sp-light-color-text-primary);
|
|
127
|
+
--card: var(--sp-light-color-bg-primary);
|
|
128
|
+
--card-foreground: var(--sp-light-color-text-primary);
|
|
129
|
+
--popover: var(--sp-light-color-bg-primary);
|
|
130
|
+
--popover-foreground: var(--sp-light-color-text-primary);
|
|
131
|
+
--primary: var(--sp-light-accent);
|
|
132
|
+
--primary-foreground: var(--sp-accent-foreground);
|
|
133
|
+
--secondary: var(--sp-light-color-bg-secondary);
|
|
134
|
+
--secondary-foreground: var(--sp-light-color-text-primary);
|
|
135
|
+
--muted: var(--sp-light-color-bg-tertiary);
|
|
136
|
+
--muted-foreground: var(--sp-light-color-text-tertiary);
|
|
137
|
+
--accent: var(--sp-light-accent);
|
|
138
|
+
--accent-foreground: var(--sp-accent-foreground);
|
|
139
|
+
--destructive: var(--sp-light-error);
|
|
140
|
+
--destructive-foreground: var(--sp-accent-foreground);
|
|
141
|
+
--border: var(--sp-light-color-border);
|
|
142
|
+
--input: var(--sp-light-color-bg-secondary);
|
|
143
|
+
--ring: var(--sp-light-accent);
|
|
144
|
+
--sidebar: var(--sp-light-sidebar);
|
|
145
|
+
--sidebar-foreground: var(--sp-light-color-text-primary);
|
|
146
|
+
--sidebar-primary: var(--sp-light-accent);
|
|
147
|
+
--sidebar-primary-foreground: var(--sp-light-color-text-primary);
|
|
148
|
+
--sidebar-accent: var(--sp-light-accent);
|
|
149
|
+
--sidebar-accent-foreground: var(--sp-light-color-text-primary);
|
|
150
|
+
--sidebar-border: var(--sp-light-color-border);
|
|
151
|
+
--sidebar-ring: var(--sp-light-accent);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.dark {
|
|
155
|
+
--sp-color-bg-primary: var(--sp-dark-color-bg-primary);
|
|
156
|
+
--sp-color-bg-secondary: var(--sp-dark-color-bg-secondary);
|
|
157
|
+
--sp-color-bg-tertiary: var(--sp-dark-color-bg-tertiary);
|
|
158
|
+
--sp-color-text-primary: var(--sp-dark-color-text-primary);
|
|
159
|
+
--sp-color-text-secondary: var(--sp-dark-color-text-secondary);
|
|
160
|
+
--sp-color-text-tertiary: var(--sp-dark-color-text-tertiary);
|
|
161
|
+
--sp-color-text-inverted: var(--sp-dark-color-text-inverted);
|
|
162
|
+
--sp-color-border: var(--sp-dark-color-border);
|
|
163
|
+
--sp-success: var(--sp-dark-success);
|
|
164
|
+
--sp-warning: var(--sp-dark-warning);
|
|
165
|
+
--sp-error: var(--sp-dark-error);
|
|
166
|
+
--sp-info: var(--sp-dark-info);
|
|
167
|
+
--sp-accent: var(--sp-dark-accent);
|
|
168
|
+
--sp-accent-hover: var(--sp-dark-accent-hover);
|
|
169
|
+
--sp-accent-active: var(--sp-dark-accent-active);
|
|
170
|
+
--sp-accent-foreground: var(--sp-dark-accent-foreground);
|
|
171
|
+
|
|
172
|
+
/* shadcn/ui dark mode variables */
|
|
173
|
+
--background: var(--sp-dark-color-bg-primary);
|
|
174
|
+
--foreground: var(--sp-dark-color-text-primary);
|
|
175
|
+
--card: var(--sp-dark-color-bg-primary);
|
|
176
|
+
--card-foreground: var(--sp-dark-color-text-primary);
|
|
177
|
+
--popover: var(--sp-dark-color-bg-primary);
|
|
178
|
+
--popover-foreground: var(--sp-dark-color-text-primary);
|
|
179
|
+
--primary: var(--sp-dark-accent);
|
|
180
|
+
--primary-foreground: var(--sp-accent-foreground);
|
|
181
|
+
--secondary: var(--sp-dark-color-bg-secondary);
|
|
182
|
+
--secondary-foreground: var(--sp-dark-color-text-primary);
|
|
183
|
+
--muted: var(--sp-dark-color-bg-tertiary);
|
|
184
|
+
--muted-foreground: var(--sp-dark-color-text-tertiary);
|
|
185
|
+
--accent: var(--sp-dark-accent);
|
|
186
|
+
--accent-foreground: var(--sp-accent-foreground);
|
|
187
|
+
--destructive: var(--sp-dark-error);
|
|
188
|
+
--destructive-foreground: var(--sp-accent-foreground);
|
|
189
|
+
--border: var(--sp-dark-color-border);
|
|
190
|
+
--input: var(--sp-dark-color-bg-secondary);
|
|
191
|
+
--ring: var(--sp-dark-accent);
|
|
192
|
+
--sidebar: var(--sp-dark-sidebar);
|
|
193
|
+
--sidebar-foreground: var(--sp-dark-color-text-primary);
|
|
194
|
+
--sidebar-primary: var(--sp-dark-accent);
|
|
195
|
+
--sidebar-primary-foreground: var(--sp-dark-color-text-primary);
|
|
196
|
+
--sidebar-accent: var(--sp-dark-accent);
|
|
197
|
+
--sidebar-accent-foreground: var(--sp-dark-color-text-primary);
|
|
198
|
+
--sidebar-border: var(--sp-dark-color-border);
|
|
199
|
+
--sidebar-ring: var(--sp-dark-accent);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@theme inline {
|
|
203
|
+
--color-sidebar: var(--sidebar);
|
|
204
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
205
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
206
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
207
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
208
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
209
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
210
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
@layer base {
|
|
214
|
+
* {
|
|
215
|
+
@apply border-border outline-ring/50;
|
|
216
|
+
}
|
|
217
|
+
body {
|
|
218
|
+
@apply bg-background text-foreground;
|
|
219
|
+
}
|
|
220
|
+
}
|