cooterlabs 1.0.0 → 1.0.1

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 ADDED
@@ -0,0 +1,61 @@
1
+ # CooterLabs UI Library CLI
2
+
3
+ A blazingly fast, standalone CLI tool for scaffolding beautiful, accessible, and highly customizable UI components built strictly for React, Vite, and Next.js applications directly into your project via the terminal!
4
+
5
+ It brings components directly to you. We do not provide a generic NPM library package to import elements from. Instead, the actual React TS/JS code is scaffolded straight into your `src/components/ui` folder, meaning you completely own the implementation, styles, and markup.
6
+
7
+ ## Features
8
+
9
+ - **✅ Project Detection**: Auto-detects your ecosystem (Next.js App router/Pages router, Vite, Create React App).
10
+ - **✅ TypeScript vs. JavaScript**: Installs `.tsx` component files if you use TypeScript, gracefully falling back to stripped `.jsx` components for vanilla JS environments.
11
+ - **✅ Tailwind Awareness**: Verifies you have Tailwind appropriately installed.
12
+ - **✅ Dependency Auto-Install**: If the component you want needs `framer-motion`, `@radix-ui/react-checkbox` or even just `clsx`, the CLI automatically resolves these dependencies silently for you.
13
+ - **✅ Prettier Formatting**: Uses your project's Prettier settings statically against our components before file creation so the component matches your exact code-style.
14
+ - **✅ Interactive Interface**: Employs interactive menus to let you visually pick the component you want to build inside your project.
15
+
16
+ ## Installation / Usage
17
+
18
+ 1. Open your terminal at the root of your project directory.
19
+ 2. Ensure you have `tailwind-merge` and `clsx` properly configured in a central utility path or installed manually (some components will auto-detect and install these).
20
+ 3. Run the CLI tool:
21
+
22
+ ```bash
23
+ npx cooterlabs add
24
+ ```
25
+
26
+ ### Direct Component Install
27
+
28
+ If you know exactly what component you want, just bypass the interactive wizard and supply the name:
29
+
30
+ ```bash
31
+ # Example: Adding a Button
32
+ npx cooterlabs add button
33
+
34
+ # Example: Adding an Avatar
35
+ npx cooterlabs add avatar
36
+ ```
37
+
38
+ ## Available Components
39
+
40
+ Currently, the CooterLabs Library ships with the following carefully designed boilerplate components:
41
+
42
+ - `avatar` - An accessible React image element holding fallback identifiers.
43
+ - `badge` - Tiny status badge elements relying on standard `variant` mappings.
44
+ - `button` - A highly stylized native HTML button leveraging Radix UI primitives (`Slot`).
45
+ - `card` - Content display wrappers comprising headers, footers, body content, and titles.
46
+ - `checkbox` - Robust implementation for accessible `<input type="checkbox"/>` mappings.
47
+ - `input` - Your standard customized HTML `<input />`.
48
+ - `label` - An accessible Radix-backed `<label />`.
49
+ - `textarea` - A matching multiline input variation styling perfectly with standard inputs.
50
+
51
+ ## How it works under-the-hood
52
+
53
+ 1. When you run `npx cooterlabs add [component]`, the script analyzes your filesystem looking for `pnpm-lock.yaml`, `bun.lockb`, `yarn.lock`, etc., and detects package managers.
54
+ 2. It looks for `tsconfig.json` to see if it should copy the raw `.tsx` component files or use regex parsers to strip types for a standard `.jsx` output.
55
+ 3. It recursively creates target folders, usually resolving to `src/components/ui` or `components/ui`.
56
+ 4. Formatting is applied.
57
+ 5. Child dependencies (e.g., `lucide-react`, Radix UI dependencies) required *exclusively* for that component are installed so the codebase isn't overwhelmed with useless imported libraries.
58
+
59
+ ## License
60
+
61
+ MIT - Grab it, use it, scale it!
package/bin/index.js CHANGED
@@ -29,6 +29,41 @@ const REGISTRY = {
29
29
  dependencies: ["lucide-react", "clsx", "tailwind-merge"],
30
30
  dev_dependencies: [],
31
31
  },
32
+ card: {
33
+ name: "Card",
34
+ dependencies: ["clsx", "tailwind-merge"],
35
+ dev_dependencies: [],
36
+ },
37
+ label: {
38
+ name: "Label",
39
+ dependencies: ["clsx", "tailwind-merge", "@radix-ui/react-label"],
40
+ dev_dependencies: [],
41
+ },
42
+ textarea: {
43
+ name: "Textarea",
44
+ dependencies: ["clsx", "tailwind-merge"],
45
+ dev_dependencies: [],
46
+ },
47
+ badge: {
48
+ name: "Badge",
49
+ dependencies: ["clsx", "tailwind-merge"],
50
+ dev_dependencies: [],
51
+ },
52
+ avatar: {
53
+ name: "Avatar",
54
+ dependencies: ["clsx", "tailwind-merge", "@radix-ui/react-avatar"],
55
+ dev_dependencies: [],
56
+ },
57
+ checkbox: {
58
+ name: "Checkbox",
59
+ dependencies: [
60
+ "lucide-react",
61
+ "clsx",
62
+ "tailwind-merge",
63
+ "@radix-ui/react-checkbox",
64
+ ],
65
+ dev_dependencies: [],
66
+ },
32
67
  };
33
68
 
34
69
  const templatesDir = path.join(__dirname, "../templates");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cooterlabs",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Component library CLI for CooterLabs apps",
5
5
  "files": [
6
6
  "bin",
@@ -19,8 +19,11 @@
19
19
  "type": "module",
20
20
  "dependencies": {
21
21
  "chalk": "^5.6.2",
22
+ "clsx": "^2.1.1",
23
+ "lucide-react": "^0.575.0",
22
24
  "ora": "^9.3.0",
23
25
  "prettier": "^3.8.1",
24
- "prompts": "^2.4.2"
26
+ "prompts": "^2.4.2",
27
+ "tailwind-merge": "^3.5.0"
25
28
  }
26
- }
29
+ }
@@ -0,0 +1,52 @@
1
+ import * as React from "react"
2
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
3
+ import { clsx, type ClassValue } from "clsx"
4
+ import { twMerge } from "tailwind-merge"
5
+
6
+ export function cn(...inputs: ClassValue[]) {
7
+ return twMerge(clsx(inputs))
8
+ }
9
+
10
+ const Avatar = React.forwardRef<
11
+ React.ElementRef<typeof AvatarPrimitive.Root>,
12
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
13
+ >(({ className, ...props }, ref) => (
14
+ <AvatarPrimitive.Root
15
+ ref={ref}
16
+ className={cn(
17
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
18
+ className
19
+ )}
20
+ {...props}
21
+ />
22
+ ))
23
+ Avatar.displayName = AvatarPrimitive.Root.displayName
24
+
25
+ const AvatarImage = React.forwardRef<
26
+ React.ElementRef<typeof AvatarPrimitive.Image>,
27
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
28
+ >(({ className, ...props }, ref) => (
29
+ <AvatarPrimitive.Image
30
+ ref={ref}
31
+ className={cn("aspect-square h-full w-full", className)}
32
+ {...props}
33
+ />
34
+ ))
35
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
36
+
37
+ const AvatarFallback = React.forwardRef<
38
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
39
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
40
+ >(({ className, ...props }, ref) => (
41
+ <AvatarPrimitive.Fallback
42
+ ref={ref}
43
+ className={cn(
44
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
45
+ className
46
+ )}
47
+ {...props}
48
+ />
49
+ ))
50
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
51
+
52
+ export { Avatar, AvatarImage, AvatarFallback }
@@ -0,0 +1,36 @@
1
+ import * as React from "react"
2
+ import { clsx, type ClassValue } from "clsx"
3
+ import { twMerge } from "tailwind-merge"
4
+
5
+ export function cn(...inputs: ClassValue[]) {
6
+ return twMerge(clsx(inputs))
7
+ }
8
+
9
+ export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
10
+ variant?: "default" | "secondary" | "destructive" | "outline"
11
+ }
12
+
13
+ const badgeVariants = {
14
+ default:
15
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
16
+ secondary:
17
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
18
+ destructive:
19
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
20
+ outline: "text-foreground",
21
+ }
22
+
23
+ function Badge({ className, variant = "default", ...props }: BadgeProps) {
24
+ return (
25
+ <div
26
+ className={cn(
27
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
28
+ badgeVariants[variant],
29
+ className
30
+ )}
31
+ {...props}
32
+ />
33
+ )
34
+ }
35
+
36
+ export { Badge }
@@ -0,0 +1,89 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "./button" // Assuming they have cn utility somewhere accessible, or we'll rewrite it to inline
4
+
5
+ export function cnFallback(...classes: (string | undefined | null | false)[]) {
6
+ return classes.filter(Boolean).join(" ")
7
+ }
8
+
9
+ // In a real scenario you'd import 'cn' from a library or a utils file.
10
+ // We'll provide an inline cn for fallback or rely on the user having ones installed:
11
+ import { clsx, type ClassValue } from "clsx"
12
+ import { twMerge } from "tailwind-merge"
13
+
14
+ function localCn(...inputs: ClassValue[]) {
15
+ return twMerge(clsx(inputs))
16
+ }
17
+
18
+ const Card = React.forwardRef<
19
+ HTMLDivElement,
20
+ React.HTMLAttributes<HTMLDivElement>
21
+ >(({ className, ...props }, ref) => (
22
+ <div
23
+ ref={ref}
24
+ className={localCn(
25
+ "rounded-xl border bg-card text-card-foreground shadow",
26
+ className
27
+ )}
28
+ {...props}
29
+ />
30
+ ))
31
+ Card.displayName = "Card"
32
+
33
+ const CardHeader = React.forwardRef<
34
+ HTMLDivElement,
35
+ React.HTMLAttributes<HTMLDivElement>
36
+ >(({ className, ...props }, ref) => (
37
+ <div
38
+ ref={ref}
39
+ className={localCn("flex flex-col space-y-1.5 p-6", className)}
40
+ {...props}
41
+ />
42
+ ))
43
+ CardHeader.displayName = "CardHeader"
44
+
45
+ const CardTitle = React.forwardRef<
46
+ HTMLParagraphElement,
47
+ React.HTMLAttributes<HTMLHeadingElement>
48
+ >(({ className, ...props }, ref) => (
49
+ <h3
50
+ ref={ref}
51
+ className={localCn("font-semibold leading-none tracking-tight", className)}
52
+ {...props}
53
+ />
54
+ ))
55
+ CardTitle.displayName = "CardTitle"
56
+
57
+ const CardDescription = React.forwardRef<
58
+ HTMLParagraphElement,
59
+ React.HTMLAttributes<HTMLParagraphElement>
60
+ >(({ className, ...props }, ref) => (
61
+ <p
62
+ ref={ref}
63
+ className={localCn("text-sm text-muted-foreground", className)}
64
+ {...props}
65
+ />
66
+ ))
67
+ CardDescription.displayName = "CardDescription"
68
+
69
+ const CardContent = React.forwardRef<
70
+ HTMLDivElement,
71
+ React.HTMLAttributes<HTMLDivElement>
72
+ >(({ className, ...props }, ref) => (
73
+ <div ref={ref} className={localCn("p-6 pt-0", className)} {...props} />
74
+ ))
75
+ CardContent.displayName = "CardContent"
76
+
77
+ const CardFooter = React.forwardRef<
78
+ HTMLDivElement,
79
+ React.HTMLAttributes<HTMLDivElement>
80
+ >(({ className, ...props }, ref) => (
81
+ <div
82
+ ref={ref}
83
+ className={localCn("flex items-center p-6 pt-0", className)}
84
+ {...props}
85
+ />
86
+ ))
87
+ CardFooter.displayName = "CardFooter"
88
+
89
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
@@ -0,0 +1,32 @@
1
+ import * as React from "react"
2
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
3
+ import { Check } from "lucide-react"
4
+ import { clsx, type ClassValue } from "clsx"
5
+ import { twMerge } from "tailwind-merge"
6
+
7
+ export function cn(...inputs: ClassValue[]) {
8
+ return twMerge(clsx(inputs))
9
+ }
10
+
11
+ const Checkbox = React.forwardRef<
12
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
13
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
14
+ >(({ className, ...props }, ref) => (
15
+ <CheckboxPrimitive.Root
16
+ ref={ref}
17
+ className={cn(
18
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
19
+ className
20
+ )}
21
+ {...props}
22
+ >
23
+ <CheckboxPrimitive.Indicator
24
+ className={cn("flex items-center justify-center text-current")}
25
+ >
26
+ <Check className="h-4 w-4" />
27
+ </CheckboxPrimitive.Indicator>
28
+ </CheckboxPrimitive.Root>
29
+ ))
30
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName
31
+
32
+ export { Checkbox }
@@ -0,0 +1,25 @@
1
+ import * as React from "react"
2
+ import * as LabelPrimitive from "@radix-ui/react-label"
3
+ import { clsx, type ClassValue } from "clsx"
4
+ import { twMerge } from "tailwind-merge"
5
+
6
+ function cn(...inputs: ClassValue[]) {
7
+ return twMerge(clsx(inputs))
8
+ }
9
+
10
+ const Label = React.forwardRef<
11
+ React.ElementRef<typeof LabelPrimitive.Root>,
12
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
13
+ >(({ className, ...props }, ref) => (
14
+ <LabelPrimitive.Root
15
+ ref={ref}
16
+ className={cn(
17
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
18
+ className
19
+ )}
20
+ {...props}
21
+ />
22
+ ))
23
+ Label.displayName = LabelPrimitive.Root.displayName
24
+
25
+ export { Label }
@@ -0,0 +1,28 @@
1
+ import * as React from "react"
2
+ import { clsx, type ClassValue } from "clsx"
3
+ import { twMerge } from "tailwind-merge"
4
+
5
+ export function cn(...inputs: ClassValue[]) {
6
+ return twMerge(clsx(inputs))
7
+ }
8
+
9
+ export interface TextareaProps
10
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement> { }
11
+
12
+ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
13
+ ({ className, ...props }, ref) => {
14
+ return (
15
+ <textarea
16
+ className={cn(
17
+ "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
18
+ className
19
+ )}
20
+ ref={ref}
21
+ {...props}
22
+ />
23
+ )
24
+ }
25
+ )
26
+ Textarea.displayName = "Textarea"
27
+
28
+ export { Textarea }