create-cs-object 0.1.1 → 0.1.3
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 +47 -2
- package/dist/cli.js +29 -2
- package/package.json +5 -2
- package/template/.gitattributes +2 -0
- package/template/AGENTS.md +11 -2
- package/template/README.md +60 -2
- package/template/authoring.md +247 -18
- package/template/components.json +25 -0
- package/template/gitignore +1 -0
- package/template/index.html +12 -0
- package/template/references/README.md +134 -0
- package/template/reports.json +8 -0
- package/template/scripts/dev.ts +104 -33
- package/template/scripts/reports.ts +24 -0
- package/template/src/App.tsx +681 -0
- package/template/src/components/theme-provider.tsx +230 -0
- package/template/src/components/ui/badge.tsx +51 -0
- package/template/src/components/ui/button.tsx +55 -0
- package/template/src/components/ui/card.tsx +102 -0
- package/template/src/components/ui/dropdown-menu.tsx +274 -0
- package/template/src/components/ui/input.tsx +19 -0
- package/template/src/components/ui/label.tsx +17 -0
- package/template/src/components/ui/separator.tsx +22 -0
- package/template/src/components/ui/sheet.tsx +137 -0
- package/template/src/hooks/use-mobile.ts +19 -0
- package/template/src/index.css +131 -0
- package/template/src/lib/utils.ts +1 -0
- package/template/src/main.tsx +9 -0
- package/template/tsconfig.app.json +29 -0
- package/template/tsconfig.json +12 -0
- package/template/tsconfig.node.json +24 -0
- package/template/vite.config.ts +144 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/* eslint-disable react-refresh/only-export-components */
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
|
|
4
|
+
type Theme = "dark" | "light" | "system"
|
|
5
|
+
type ResolvedTheme = "dark" | "light"
|
|
6
|
+
|
|
7
|
+
type ThemeProviderProps = {
|
|
8
|
+
children: React.ReactNode
|
|
9
|
+
defaultTheme?: Theme
|
|
10
|
+
storageKey?: string
|
|
11
|
+
disableTransitionOnChange?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type ThemeProviderState = {
|
|
15
|
+
theme: Theme
|
|
16
|
+
setTheme: (theme: Theme) => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const COLOR_SCHEME_QUERY = "(prefers-color-scheme: dark)"
|
|
20
|
+
const THEME_VALUES: Theme[] = ["dark", "light", "system"]
|
|
21
|
+
|
|
22
|
+
const ThemeProviderContext = React.createContext<
|
|
23
|
+
ThemeProviderState | undefined
|
|
24
|
+
>(undefined)
|
|
25
|
+
|
|
26
|
+
function isTheme(value: string | null): value is Theme {
|
|
27
|
+
if (value === null) {
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return THEME_VALUES.includes(value as Theme)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getSystemTheme(): ResolvedTheme {
|
|
35
|
+
if (window.matchMedia(COLOR_SCHEME_QUERY).matches) {
|
|
36
|
+
return "dark"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return "light"
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function disableTransitionsTemporarily() {
|
|
43
|
+
const style = document.createElement("style")
|
|
44
|
+
style.appendChild(
|
|
45
|
+
document.createTextNode(
|
|
46
|
+
"*,*::before,*::after{-webkit-transition:none!important;transition:none!important}"
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
document.head.appendChild(style)
|
|
50
|
+
|
|
51
|
+
return () => {
|
|
52
|
+
window.getComputedStyle(document.body)
|
|
53
|
+
requestAnimationFrame(() => {
|
|
54
|
+
requestAnimationFrame(() => {
|
|
55
|
+
style.remove()
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isEditableTarget(target: EventTarget | null) {
|
|
62
|
+
if (!(target instanceof HTMLElement)) {
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (target.isContentEditable) {
|
|
67
|
+
return true
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const editableParent = target.closest(
|
|
71
|
+
"input, textarea, select, [contenteditable='true']"
|
|
72
|
+
)
|
|
73
|
+
if (editableParent) {
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function ThemeProvider({
|
|
81
|
+
children,
|
|
82
|
+
defaultTheme = "system",
|
|
83
|
+
storageKey = "theme",
|
|
84
|
+
disableTransitionOnChange = true,
|
|
85
|
+
...props
|
|
86
|
+
}: ThemeProviderProps) {
|
|
87
|
+
const [theme, setThemeState] = React.useState<Theme>(() => {
|
|
88
|
+
const storedTheme = localStorage.getItem(storageKey)
|
|
89
|
+
if (isTheme(storedTheme)) {
|
|
90
|
+
return storedTheme
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return defaultTheme
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const setTheme = React.useCallback(
|
|
97
|
+
(nextTheme: Theme) => {
|
|
98
|
+
localStorage.setItem(storageKey, nextTheme)
|
|
99
|
+
setThemeState(nextTheme)
|
|
100
|
+
},
|
|
101
|
+
[storageKey]
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
const applyTheme = React.useCallback(
|
|
105
|
+
(nextTheme: Theme) => {
|
|
106
|
+
const root = document.documentElement
|
|
107
|
+
const resolvedTheme =
|
|
108
|
+
nextTheme === "system" ? getSystemTheme() : nextTheme
|
|
109
|
+
const restoreTransitions = disableTransitionOnChange
|
|
110
|
+
? disableTransitionsTemporarily()
|
|
111
|
+
: null
|
|
112
|
+
|
|
113
|
+
root.classList.remove("light", "dark")
|
|
114
|
+
root.classList.add(resolvedTheme)
|
|
115
|
+
|
|
116
|
+
if (restoreTransitions) {
|
|
117
|
+
restoreTransitions()
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
[disableTransitionOnChange]
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
React.useEffect(() => {
|
|
124
|
+
applyTheme(theme)
|
|
125
|
+
|
|
126
|
+
if (theme !== "system") {
|
|
127
|
+
return undefined
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const mediaQuery = window.matchMedia(COLOR_SCHEME_QUERY)
|
|
131
|
+
const handleChange = () => {
|
|
132
|
+
applyTheme("system")
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
mediaQuery.addEventListener("change", handleChange)
|
|
136
|
+
|
|
137
|
+
return () => {
|
|
138
|
+
mediaQuery.removeEventListener("change", handleChange)
|
|
139
|
+
}
|
|
140
|
+
}, [theme, applyTheme])
|
|
141
|
+
|
|
142
|
+
React.useEffect(() => {
|
|
143
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
144
|
+
if (event.repeat) {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (event.metaKey || event.ctrlKey || event.altKey) {
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (isEditableTarget(event.target)) {
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (event.key.toLowerCase() !== "d") {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
setThemeState((currentTheme) => {
|
|
161
|
+
const nextTheme =
|
|
162
|
+
currentTheme === "dark"
|
|
163
|
+
? "light"
|
|
164
|
+
: currentTheme === "light"
|
|
165
|
+
? "dark"
|
|
166
|
+
: getSystemTheme() === "dark"
|
|
167
|
+
? "light"
|
|
168
|
+
: "dark"
|
|
169
|
+
|
|
170
|
+
localStorage.setItem(storageKey, nextTheme)
|
|
171
|
+
return nextTheme
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
window.addEventListener("keydown", handleKeyDown)
|
|
176
|
+
|
|
177
|
+
return () => {
|
|
178
|
+
window.removeEventListener("keydown", handleKeyDown)
|
|
179
|
+
}
|
|
180
|
+
}, [storageKey])
|
|
181
|
+
|
|
182
|
+
React.useEffect(() => {
|
|
183
|
+
const handleStorageChange = (event: StorageEvent) => {
|
|
184
|
+
if (event.storageArea !== localStorage) {
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (event.key !== storageKey) {
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (isTheme(event.newValue)) {
|
|
193
|
+
setThemeState(event.newValue)
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
setThemeState(defaultTheme)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
window.addEventListener("storage", handleStorageChange)
|
|
201
|
+
|
|
202
|
+
return () => {
|
|
203
|
+
window.removeEventListener("storage", handleStorageChange)
|
|
204
|
+
}
|
|
205
|
+
}, [defaultTheme, storageKey])
|
|
206
|
+
|
|
207
|
+
const value = React.useMemo(
|
|
208
|
+
() => ({
|
|
209
|
+
theme,
|
|
210
|
+
setTheme,
|
|
211
|
+
}),
|
|
212
|
+
[theme, setTheme]
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<ThemeProviderContext.Provider {...props} value={value}>
|
|
217
|
+
{children}
|
|
218
|
+
</ThemeProviderContext.Provider>
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export const useTheme = () => {
|
|
223
|
+
const context = React.useContext(ThemeProviderContext)
|
|
224
|
+
|
|
225
|
+
if (context === undefined) {
|
|
226
|
+
throw new Error("useTheme must be used within a ThemeProvider")
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return context
|
|
230
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { mergeProps } from "@base-ui/react/merge-props"
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render"
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
+
import { cn } from "cn"
|
|
5
|
+
|
|
6
|
+
const badgeVariants = cva(
|
|
7
|
+
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-none border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
|
12
|
+
secondary:
|
|
13
|
+
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
|
14
|
+
destructive:
|
|
15
|
+
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
|
|
16
|
+
outline:
|
|
17
|
+
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
|
|
18
|
+
ghost:
|
|
19
|
+
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
|
20
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
defaultVariants: {
|
|
24
|
+
variant: "default",
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
function Badge({
|
|
30
|
+
className,
|
|
31
|
+
variant = "default",
|
|
32
|
+
render,
|
|
33
|
+
...props
|
|
34
|
+
}: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
|
|
35
|
+
return useRender({
|
|
36
|
+
defaultTagName: "span",
|
|
37
|
+
props: mergeProps<"span">(
|
|
38
|
+
{
|
|
39
|
+
className: cn(badgeVariants({ variant }), className),
|
|
40
|
+
},
|
|
41
|
+
props
|
|
42
|
+
),
|
|
43
|
+
render,
|
|
44
|
+
state: {
|
|
45
|
+
slot: "badge",
|
|
46
|
+
variant,
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export { Badge, badgeVariants }
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Button as ButtonPrimitive } from "@base-ui/react/button"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cn } from "cn"
|
|
4
|
+
|
|
5
|
+
const buttonVariants = cva(
|
|
6
|
+
"group/button inline-flex shrink-0 items-center justify-center rounded-none border border-transparent bg-clip-padding text-xs font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-1 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
|
11
|
+
outline:
|
|
12
|
+
"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
13
|
+
secondary:
|
|
14
|
+
"bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
15
|
+
ghost:
|
|
16
|
+
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
17
|
+
destructive:
|
|
18
|
+
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
|
19
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
20
|
+
},
|
|
21
|
+
size: {
|
|
22
|
+
default:
|
|
23
|
+
"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
24
|
+
xs: "h-6 gap-1 rounded-none px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
25
|
+
sm: "h-7 gap-1 rounded-none px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
|
26
|
+
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
27
|
+
icon: "size-8",
|
|
28
|
+
"icon-xs": "size-6 rounded-none [&_svg:not([class*='size-'])]:size-3",
|
|
29
|
+
"icon-sm": "size-7 rounded-none",
|
|
30
|
+
"icon-lg": "size-9",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
variant: "default",
|
|
35
|
+
size: "default",
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
function Button({
|
|
41
|
+
className,
|
|
42
|
+
variant = "default",
|
|
43
|
+
size = "default",
|
|
44
|
+
...props
|
|
45
|
+
}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
|
|
46
|
+
return (
|
|
47
|
+
<ButtonPrimitive
|
|
48
|
+
data-slot="button"
|
|
49
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { Button, buttonVariants }
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "cn"
|
|
3
|
+
|
|
4
|
+
function Card({
|
|
5
|
+
className,
|
|
6
|
+
size = "default",
|
|
7
|
+
...props
|
|
8
|
+
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
|
|
9
|
+
return (
|
|
10
|
+
<div
|
|
11
|
+
data-slot="card"
|
|
12
|
+
data-size={size}
|
|
13
|
+
className={cn(
|
|
14
|
+
"group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-none bg-card py-(--card-spacing) text-xs/relaxed text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-none *:[img:last-child]:rounded-none",
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
data-slot="card-header"
|
|
26
|
+
className={cn(
|
|
27
|
+
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-none px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",
|
|
28
|
+
className
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
data-slot="card-title"
|
|
39
|
+
className={cn(
|
|
40
|
+
"font-heading text-sm font-medium group-data-[size=sm]/card:text-sm",
|
|
41
|
+
className
|
|
42
|
+
)}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
data-slot="card-description"
|
|
52
|
+
className={cn("text-xs/relaxed text-muted-foreground", className)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
data-slot="card-action"
|
|
62
|
+
className={cn(
|
|
63
|
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
64
|
+
className
|
|
65
|
+
)}
|
|
66
|
+
{...props}
|
|
67
|
+
/>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
72
|
+
return (
|
|
73
|
+
<div
|
|
74
|
+
data-slot="card-content"
|
|
75
|
+
className={cn("px-(--card-spacing)", className)}
|
|
76
|
+
{...props}
|
|
77
|
+
/>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
82
|
+
return (
|
|
83
|
+
<div
|
|
84
|
+
data-slot="card-footer"
|
|
85
|
+
className={cn(
|
|
86
|
+
"flex items-center rounded-none border-t p-(--card-spacing)",
|
|
87
|
+
className
|
|
88
|
+
)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export {
|
|
95
|
+
Card,
|
|
96
|
+
CardHeader,
|
|
97
|
+
CardFooter,
|
|
98
|
+
CardTitle,
|
|
99
|
+
CardAction,
|
|
100
|
+
CardDescription,
|
|
101
|
+
CardContent,
|
|
102
|
+
}
|