create-cs-object 0.1.1 → 0.1.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.
@@ -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
+ }