@srcroot/ui 0.0.65 → 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 +117 -92
- package/dist/index.js +166 -12
- package/package.json +2 -2
- package/src/registry/analytics/google-analytics.vite.tsx +35 -0
- package/src/registry/analytics/google-tag-manager.vite.tsx +50 -0
- package/src/registry/analytics/meta-pixel.vite.tsx +38 -0
- package/src/registry/analytics/microsoft-clarity.vite.tsx +36 -0
- package/src/registry/analytics/tiktok-pixel.vite.tsx +39 -0
- package/src/registry/ui/theme-switcher.vite.tsx +73 -0
package/README.md
CHANGED
|
@@ -1,146 +1,171 @@
|
|
|
1
1
|
# @srcroot/ui
|
|
2
2
|
|
|
3
|
-
A UI library with polymorphic, accessible React components.
|
|
4
|
-
This library provides a collection of re-usable components that you can copy and paste into your apps.
|
|
3
|
+
A UI library with polymorphic, accessible React components for Next.js, Vite, and other React frameworks.
|
|
5
4
|
|
|
6
5
|
## Features
|
|
7
6
|
|
|
8
|
-
- **
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
7
|
+
- **Framework-Aware**: Automatically detects your framework (Next.js, Vite, etc.) and uses the right component variants
|
|
8
|
+
- **Polymorphic**: Most components support an `as` prop (e.g., render a `Button` as an `a` tag)
|
|
9
|
+
- **Accessible**: Built on standard HTML elements and WAI-ARIA patterns
|
|
10
|
+
- **Copy/Paste**: Components are copied directly into your project — you own the code
|
|
11
|
+
- **Styled**: Beautiful defaults using Tailwind CSS and `class-variance-authority`
|
|
12
|
+
- **Multi-Platform**: Supports npm, Yarn, pnpm, Bun, and Deno
|
|
12
13
|
|
|
13
|
-
##
|
|
14
|
-
|
|
15
|
-
This library is distributed via a CLI that initializes your project and adds components directly to your source code.
|
|
14
|
+
## Quick Start
|
|
16
15
|
|
|
17
16
|
### 1. Initialize
|
|
18
17
|
|
|
19
|
-
Run the `init` command to set up the necessary dependencies and structural files (like `cn` utility) in your project.
|
|
20
|
-
|
|
21
18
|
```bash
|
|
22
19
|
npx @srcroot/ui init
|
|
23
20
|
```
|
|
24
21
|
|
|
25
|
-
This
|
|
22
|
+
This sets up your project with the `cn` utility, theme configuration, and creates the necessary structure.
|
|
26
23
|
|
|
27
24
|
### 2. Add Components
|
|
28
25
|
|
|
29
|
-
Use the `add` command to install components. You can do this in three ways:
|
|
30
|
-
|
|
31
|
-
**Interactive Mode:**
|
|
32
|
-
Run without arguments to select components from a list.
|
|
33
26
|
```bash
|
|
27
|
+
# Interactive mode
|
|
34
28
|
npx @srcroot/ui add
|
|
35
|
-
```
|
|
36
29
|
|
|
37
|
-
|
|
38
|
-
Add one or more components by name.
|
|
39
|
-
```bash
|
|
30
|
+
# Specific components
|
|
40
31
|
npx @srcroot/ui add button card input
|
|
41
|
-
```
|
|
42
32
|
|
|
43
|
-
|
|
44
|
-
Install every available component at once.
|
|
45
|
-
```bash
|
|
33
|
+
# Add all components
|
|
46
34
|
npx @srcroot/ui add --all
|
|
47
35
|
```
|
|
48
36
|
|
|
49
|
-
|
|
37
|
+
Components are installed to `src/components/ui/` (or `components/ui/` depending on your structure).
|
|
50
38
|
|
|
51
|
-
|
|
39
|
+
## Supported Package Managers
|
|
52
40
|
|
|
53
|
-
|
|
41
|
+
The CLI automatically detects and uses your package manager:
|
|
54
42
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
43
|
+
- **npm** - `npm install`
|
|
44
|
+
- **Yarn** - `yarn add`
|
|
45
|
+
- **pnpm** - `pnpm add`
|
|
46
|
+
- **Bun** - `bun add`
|
|
47
|
+
- **Deno** - `deno add -A`
|
|
48
|
+
|
|
49
|
+
## Framework Detection
|
|
50
|
+
|
|
51
|
+
The CLI automatically detects your framework:
|
|
52
|
+
|
|
53
|
+
| Framework | Detection | Notes |
|
|
54
|
+
|-----------|-----------|-------|
|
|
55
|
+
| Next.js | `next.config.ts/js/mjs` or `src/app` directory | Uses `next/script` for analytics |
|
|
56
|
+
| Vite | `vite.config.ts/js/mjs` | Uses `useEffect` for analytics, localStorage for themes |
|
|
66
57
|
|
|
67
58
|
## Available Components
|
|
68
59
|
|
|
69
|
-
|
|
60
|
+
Run `npx @srcroot/ui list` to see all available components.
|
|
61
|
+
|
|
62
|
+
### Analytics
|
|
63
|
+
- `google-analytics` - Google Analytics 4 integration
|
|
64
|
+
- `google-tag-manager` - Google Tag Manager container
|
|
65
|
+
- `meta-pixel` - Meta/Facebook Pixel tracking
|
|
66
|
+
- `microsoft-clarity` - Microsoft Clarity analytics
|
|
67
|
+
- `tiktok-pixel` - TikTok Pixel tracking
|
|
70
68
|
|
|
71
69
|
### Core
|
|
72
|
-
- `button` - Polymorphic button with variants
|
|
73
|
-
- `badge` - Status indicators
|
|
74
|
-
- `avatar` - User profile images with fallbacks
|
|
75
|
-
- `separator` - Visual divider
|
|
76
|
-
- `button-group` - Attached or spaced button sets
|
|
70
|
+
- `button` - Polymorphic button with variants
|
|
71
|
+
- `badge` - Status indicators
|
|
72
|
+
- `avatar` - User profile images with fallbacks
|
|
73
|
+
- `separator` - Visual divider
|
|
74
|
+
- `button-group` - Attached or spaced button sets
|
|
75
|
+
- `slot` - Polymorphic slot for component composition
|
|
77
76
|
|
|
78
77
|
### Forms
|
|
79
|
-
- `input` - Basic text input
|
|
80
|
-
- `textarea` - Multi-line text input
|
|
81
|
-
- `checkbox` - Toggle selection
|
|
82
|
-
- `radio` - Single selection from list
|
|
83
|
-
- `switch` - Toggle switch
|
|
84
|
-
- `select` - Dropdown selection
|
|
85
|
-
- `slider` - Range input
|
|
86
|
-
- `otp-input` - One-time password verification
|
|
87
|
-
- `search` - Search input with debounce
|
|
88
|
-
- `calendar` - Date and range picker
|
|
78
|
+
- `input` - Basic text input
|
|
79
|
+
- `textarea` - Multi-line text input
|
|
80
|
+
- `checkbox` - Toggle selection
|
|
81
|
+
- `radio` - Single selection from list
|
|
82
|
+
- `switch` - Toggle switch
|
|
83
|
+
- `select` - Dropdown selection
|
|
84
|
+
- `slider` - Range input
|
|
85
|
+
- `otp-input` - One-time password verification
|
|
86
|
+
- `search` - Search input with debounce
|
|
87
|
+
- `calendar` - Date and range picker
|
|
88
|
+
- `date-picker` - Date selection component
|
|
89
|
+
- `form-field` - Form field wrapper with label and validation
|
|
90
|
+
- `input-group` - Input with attached elements
|
|
89
91
|
|
|
90
92
|
### Layout
|
|
91
|
-
- `card` - Content container with header/content/footer
|
|
92
|
-
- `container` - Centered layout wrapper
|
|
93
|
-
- `aspect-ratio` - Maintain element proportions
|
|
93
|
+
- `card` - Content container with header/content/footer
|
|
94
|
+
- `container` - Centered layout wrapper
|
|
95
|
+
- `aspect-ratio` - Maintain element proportions
|
|
96
|
+
- `resizable` - Resizable panel groups
|
|
94
97
|
|
|
95
98
|
### Data Display
|
|
96
|
-
- `text` - Polymorphic typography
|
|
97
|
-
- `label` - Accessible form label
|
|
98
|
-
- `table` - Responsive data table
|
|
99
|
-
- `
|
|
100
|
-
- `
|
|
101
|
-
- `
|
|
102
|
-
- `
|
|
103
|
-
- `
|
|
104
|
-
- `
|
|
105
|
-
- `
|
|
99
|
+
- `text` - Polymorphic typography
|
|
100
|
+
- `label` - Accessible form label
|
|
101
|
+
- `table` - Responsive data table
|
|
102
|
+
- `table-of-contents` - Auto-generated TOC
|
|
103
|
+
- `accordion` - Collapsible content sections
|
|
104
|
+
- `collapsible` - Expandable panel
|
|
105
|
+
- `tabs` - Tabbed content switcher
|
|
106
|
+
- `progress` - Progress bar
|
|
107
|
+
- `skeleton` - Loading placeholder
|
|
108
|
+
- `image` - Enhanced image with fallback
|
|
109
|
+
- `carousel` - Content slider with autoplay
|
|
110
|
+
- `marquee` - Scrolling marquee animation
|
|
106
111
|
|
|
107
112
|
### Feedback
|
|
108
|
-
- `loading-spinner` - SVG spinner with variants
|
|
109
|
-
- `star-rating` - Interactive rating component
|
|
110
|
-
- `toast` - Transient notifications
|
|
111
|
-
- `alert` - Critical information banner
|
|
113
|
+
- `loading-spinner` - SVG spinner with variants
|
|
114
|
+
- `star-rating` - Interactive rating component
|
|
115
|
+
- `toast` - Transient notifications
|
|
116
|
+
- `alert` - Critical information banner
|
|
117
|
+
- `scroll-to-top` - Scroll to top button
|
|
118
|
+
- `scroll-animation` - Scroll-triggered animations
|
|
112
119
|
|
|
113
120
|
### Overlays
|
|
114
|
-
- `dialog` - Modal dialog
|
|
115
|
-
- `alert-dialog` - Modal for confirming actions
|
|
116
|
-
- `sheet` - Side-panel overlay
|
|
117
|
-
- `popover` - Content appearing over trigger
|
|
118
|
-
- `tooltip` - Hover information
|
|
119
|
-
- `dropdown-menu` - Menu for actions/navigation
|
|
121
|
+
- `dialog` - Modal dialog
|
|
122
|
+
- `alert-dialog` - Modal for confirming actions
|
|
123
|
+
- `sheet` - Side-panel overlay
|
|
124
|
+
- `popover` - Content appearing over trigger
|
|
125
|
+
- `tooltip` - Hover information
|
|
126
|
+
- `dropdown-menu` - Menu for actions/navigation
|
|
127
|
+
- `context-menu` - Right-click context menu
|
|
120
128
|
|
|
121
129
|
### Navigation
|
|
122
|
-
- `breadcrumb` - Navigation trail
|
|
123
|
-
- `pagination` - Page navigation controls
|
|
130
|
+
- `breadcrumb` - Navigation trail
|
|
131
|
+
- `pagination` - Page navigation controls
|
|
132
|
+
- `sidebar` - Collapsible sidebar with navigation
|
|
133
|
+
- `menubar` - Menu bar component
|
|
134
|
+
|
|
135
|
+
### Specialized
|
|
136
|
+
- `chatbot` - Chat interface widget
|
|
137
|
+
- `chart` - Recharts-based chart component
|
|
138
|
+
- `map` - Google Maps embed
|
|
139
|
+
- `combobox` - Searchable dropdown (Command menu pattern)
|
|
140
|
+
- `command` - Command menu (Cmdk-style)
|
|
141
|
+
- `file-upload` - Drag-and-drop file upload
|
|
142
|
+
- `hover-card` - Hover-triggered card
|
|
143
|
+
- `native-select` - Native select wrapper
|
|
144
|
+
- `patterns` - Decorative background patterns
|
|
145
|
+
- `theme-switcher` - Light/dark/system theme toggle
|
|
146
|
+
- `whatsapp` - WhatsApp floating button
|
|
147
|
+
- `floating-dock` - macOS-style floating dock
|
|
124
148
|
|
|
125
149
|
## Polymorphism
|
|
126
150
|
|
|
127
|
-
|
|
151
|
+
Components accept an `as` prop to change the underlying HTML element:
|
|
128
152
|
|
|
129
153
|
```tsx
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
</Button>
|
|
134
|
-
|
|
135
|
-
// Renders as a specialized text variant
|
|
136
|
-
<Text as="h1" variant="h1">
|
|
137
|
-
Page Title
|
|
138
|
-
</Text>
|
|
154
|
+
<Button as="a" href="/login">Login</Button>
|
|
155
|
+
|
|
156
|
+
<Text as="h1" variant="h1">Page Title</Text>
|
|
139
157
|
```
|
|
140
158
|
|
|
141
|
-
##
|
|
159
|
+
## CLI Options
|
|
142
160
|
|
|
143
|
-
|
|
161
|
+
```
|
|
162
|
+
npx @srcroot/ui init Initialize project structure
|
|
163
|
+
npx @srcroot/ui add [comps] Add component(s)
|
|
164
|
+
npx @srcroot/ui add --all Add all components
|
|
165
|
+
npx @srcroot/ui list List available components
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Local Development
|
|
144
169
|
|
|
145
170
|
```bash
|
|
146
171
|
cd examples/playground
|
|
@@ -148,4 +173,4 @@ npm install
|
|
|
148
173
|
npm run dev
|
|
149
174
|
```
|
|
150
175
|
|
|
151
|
-
Visit `http://localhost:3001` to view the component showcase.
|
|
176
|
+
Visit `http://localhost:3001` to view the component showcase.
|
package/dist/index.js
CHANGED
|
@@ -101,15 +101,13 @@ var ThemeService = class {
|
|
|
101
101
|
};
|
|
102
102
|
|
|
103
103
|
// src/cli/utils/templates.ts
|
|
104
|
-
|
|
104
|
+
function getTailwindConfig(framework) {
|
|
105
|
+
const contentPaths = framework === "vite" ? ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"] : ["./src/**/*.{js,ts,jsx,tsx,mdx}", "./app/**/*.{js,ts,jsx,tsx,mdx}", "./components/**/*.{js,ts,jsx,tsx,mdx}"];
|
|
106
|
+
return `import type { Config } from "tailwindcss"
|
|
105
107
|
|
|
106
108
|
const config: Config = {
|
|
107
109
|
darkMode: ["class"],
|
|
108
|
-
content: [
|
|
109
|
-
"./src/**/*.{js,ts,jsx,tsx,mdx}",
|
|
110
|
-
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
|
111
|
-
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
|
112
|
-
],
|
|
110
|
+
content: ${JSON.stringify(contentPaths, null, 6).replace(/\n/g, "\n ").replace(/\[$/, " [").replace(/\]$/, " ]")},
|
|
113
111
|
theme: {
|
|
114
112
|
extend: {
|
|
115
113
|
colors: {
|
|
@@ -173,12 +171,15 @@ const config: Config = {
|
|
|
173
171
|
|
|
174
172
|
export default config
|
|
175
173
|
`;
|
|
174
|
+
}
|
|
175
|
+
var TAILWIND_CONFIG = getTailwindConfig("nextjs");
|
|
176
176
|
|
|
177
177
|
// src/cli/utils/get-package-manager.ts
|
|
178
178
|
import fs3 from "fs";
|
|
179
179
|
import path3 from "path";
|
|
180
180
|
function getPackageManager(cwd) {
|
|
181
181
|
const dir = cwd || process.cwd();
|
|
182
|
+
if (fs3.existsSync(path3.join(dir, "deno.json")) || fs3.existsSync(path3.join(dir, "deno.jsonc"))) return "deno";
|
|
182
183
|
if (fs3.existsSync(path3.join(dir, "bun.lockb"))) return "bun";
|
|
183
184
|
if (fs3.existsSync(path3.join(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
184
185
|
if (fs3.existsSync(path3.join(dir, "yarn.lock"))) return "yarn";
|
|
@@ -189,6 +190,8 @@ function getPackageManager(cwd) {
|
|
|
189
190
|
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
190
191
|
if (userAgent.startsWith("bun")) return "bun";
|
|
191
192
|
}
|
|
193
|
+
const denoAgent = process.env.DENO_ENV;
|
|
194
|
+
if (denoAgent) return "deno";
|
|
192
195
|
return "npm";
|
|
193
196
|
}
|
|
194
197
|
|
|
@@ -261,10 +264,23 @@ var ProjectInitializer = class {
|
|
|
261
264
|
process.exit(1);
|
|
262
265
|
}
|
|
263
266
|
}
|
|
267
|
+
detectFramework(cwd) {
|
|
268
|
+
if (fs5.existsSync(path5.join(cwd, "next.config.ts")) || fs5.existsSync(path5.join(cwd, "next.config.js")) || fs5.existsSync(path5.join(cwd, "next.config.mjs"))) {
|
|
269
|
+
return "nextjs";
|
|
270
|
+
}
|
|
271
|
+
if (fs5.existsSync(path5.join(cwd, "vite.config.ts")) || fs5.existsSync(path5.join(cwd, "vite.config.js")) || fs5.existsSync(path5.join(cwd, "vite.config.mjs"))) {
|
|
272
|
+
return "vite";
|
|
273
|
+
}
|
|
274
|
+
if (fs5.existsSync(path5.join(cwd, "src", "app"))) {
|
|
275
|
+
return "nextjs";
|
|
276
|
+
}
|
|
277
|
+
return "unknown";
|
|
278
|
+
}
|
|
264
279
|
async detectConfiguration() {
|
|
265
280
|
const cwd = path5.resolve(this.options.cwd);
|
|
266
281
|
const packageManager = getPackageManager(cwd);
|
|
267
282
|
const installCmd = packageManager === "npm" ? "install" : "add";
|
|
283
|
+
const framework = this.detectFramework(cwd);
|
|
268
284
|
const pkg = await fs5.readJson(path5.join(cwd, "package.json"));
|
|
269
285
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
270
286
|
const tailwindVersion = allDeps["tailwindcss"] || "";
|
|
@@ -303,7 +319,8 @@ var ProjectInitializer = class {
|
|
|
303
319
|
hasPagesDir,
|
|
304
320
|
libDir,
|
|
305
321
|
componentsDir,
|
|
306
|
-
globalsPath
|
|
322
|
+
globalsPath,
|
|
323
|
+
framework
|
|
307
324
|
};
|
|
308
325
|
}
|
|
309
326
|
async promptUser() {
|
|
@@ -370,7 +387,7 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
370
387
|
if (!cfg.isTailwind4) {
|
|
371
388
|
spinner.start("Setting up Tailwind config...");
|
|
372
389
|
const tailwindConfigPath = path5.join(cfg.cwd, "tailwind.config.ts");
|
|
373
|
-
await fs5.writeFile(tailwindConfigPath,
|
|
390
|
+
await fs5.writeFile(tailwindConfigPath, getTailwindConfig(cfg.framework));
|
|
374
391
|
spinner.succeed(`Created tailwind.config.ts`);
|
|
375
392
|
}
|
|
376
393
|
const packageInfo = getPackageInfo();
|
|
@@ -924,14 +941,123 @@ var REGISTRY = {
|
|
|
924
941
|
|
|
925
942
|
// src/cli/services/component-adder.ts
|
|
926
943
|
var __dirname4 = path6.dirname(fileURLToPath5(import.meta.url));
|
|
944
|
+
var FRAMEWORK_SPECIFIC_COMPONENTS = {
|
|
945
|
+
"google-analytics": "analytics/google-analytics.vite.tsx",
|
|
946
|
+
"google-tag-manager": "analytics/google-tag-manager.vite.tsx",
|
|
947
|
+
"meta-pixel": "analytics/meta-pixel.vite.tsx",
|
|
948
|
+
"microsoft-clarity": "analytics/microsoft-clarity.vite.tsx",
|
|
949
|
+
"tiktok-pixel": "analytics/tiktok-pixel.vite.tsx",
|
|
950
|
+
"theme-switcher": "ui/theme-switcher.vite.tsx"
|
|
951
|
+
};
|
|
927
952
|
var ComponentAdder = class {
|
|
928
953
|
cwd;
|
|
929
954
|
options;
|
|
955
|
+
framework = "unknown";
|
|
930
956
|
constructor(cwd, options) {
|
|
931
957
|
this.cwd = cwd;
|
|
932
958
|
this.options = options;
|
|
959
|
+
this.framework = this.detectFramework();
|
|
960
|
+
}
|
|
961
|
+
detectFramework() {
|
|
962
|
+
if (fs6.existsSync(path6.join(this.cwd, "next.config.ts")) || fs6.existsSync(path6.join(this.cwd, "next.config.js")) || fs6.existsSync(path6.join(this.cwd, "next.config.mjs"))) {
|
|
963
|
+
return "nextjs";
|
|
964
|
+
}
|
|
965
|
+
if (fs6.existsSync(path6.join(this.cwd, "vite.config.ts")) || fs6.existsSync(path6.join(this.cwd, "vite.config.js")) || fs6.existsSync(path6.join(this.cwd, "vite.config.mjs"))) {
|
|
966
|
+
return "vite";
|
|
967
|
+
}
|
|
968
|
+
if (fs6.existsSync(path6.join(this.cwd, "src", "app"))) {
|
|
969
|
+
return "nextjs";
|
|
970
|
+
}
|
|
971
|
+
return "unknown";
|
|
972
|
+
}
|
|
973
|
+
transformForVite(name, content) {
|
|
974
|
+
content = content.replace(/^"use client"[;\n\r]*/m, "");
|
|
975
|
+
content = content.replace(/import\s*{\s*useTheme\s*}\s*from\s*"next-themes"\s*;?\n?/g, "");
|
|
976
|
+
content = content.replace(/import\s*{\s*ThemeProvider\s*}\s*from\s*"next-themes"\s*;?\n?/g, "");
|
|
977
|
+
content = content.replace(/import\s*{\s*cn\s*}\s*from\s*"@\/lib\/utils"\s*;?\n?/g, 'import { cn } from "../../lib/utils"\n');
|
|
978
|
+
content = content.replace(/import\s*{\s*Slot\s*}\s*from\s*"@\/components\/ui\/slot"\s*;?\n?/g, 'import { Slot } from "./slot"\n');
|
|
979
|
+
const otherUiImports = [
|
|
980
|
+
"accordion",
|
|
981
|
+
"alert-dialog",
|
|
982
|
+
"alert",
|
|
983
|
+
"aspect-ratio",
|
|
984
|
+
"avatar",
|
|
985
|
+
"badge",
|
|
986
|
+
"breadcrumb",
|
|
987
|
+
"button",
|
|
988
|
+
"button-group",
|
|
989
|
+
"calendar",
|
|
990
|
+
"card",
|
|
991
|
+
"carousel",
|
|
992
|
+
"chart",
|
|
993
|
+
"chatbot",
|
|
994
|
+
"checkbox",
|
|
995
|
+
"collapsible",
|
|
996
|
+
"combobox",
|
|
997
|
+
"command",
|
|
998
|
+
"container",
|
|
999
|
+
"context-menu",
|
|
1000
|
+
"date-picker",
|
|
1001
|
+
"dialog",
|
|
1002
|
+
"drawer",
|
|
1003
|
+
"dropdown-menu",
|
|
1004
|
+
"empty-state",
|
|
1005
|
+
"file-upload",
|
|
1006
|
+
"floating-dock",
|
|
1007
|
+
"form-field",
|
|
1008
|
+
"hover-card",
|
|
1009
|
+
"image",
|
|
1010
|
+
"input",
|
|
1011
|
+
"input-group",
|
|
1012
|
+
"kbd",
|
|
1013
|
+
"label",
|
|
1014
|
+
"loading-spinner",
|
|
1015
|
+
"map",
|
|
1016
|
+
"marquee",
|
|
1017
|
+
"menubar",
|
|
1018
|
+
"native-select",
|
|
1019
|
+
"otp-input",
|
|
1020
|
+
"pagination",
|
|
1021
|
+
"patterns",
|
|
1022
|
+
"popover",
|
|
1023
|
+
"progress",
|
|
1024
|
+
"radio",
|
|
1025
|
+
"resizable",
|
|
1026
|
+
"scroll-animation",
|
|
1027
|
+
"scroll-area",
|
|
1028
|
+
"scroll-to-top",
|
|
1029
|
+
"search",
|
|
1030
|
+
"select",
|
|
1031
|
+
"separator",
|
|
1032
|
+
"sheet",
|
|
1033
|
+
"sidebar",
|
|
1034
|
+
"skeleton",
|
|
1035
|
+
"slider",
|
|
1036
|
+
"star-rating",
|
|
1037
|
+
"switch",
|
|
1038
|
+
"table",
|
|
1039
|
+
"table-of-contents",
|
|
1040
|
+
"tabs",
|
|
1041
|
+
"text",
|
|
1042
|
+
"textarea",
|
|
1043
|
+
"toast",
|
|
1044
|
+
"toggle",
|
|
1045
|
+
"toggle-group",
|
|
1046
|
+
"tooltip",
|
|
1047
|
+
"whatsapp"
|
|
1048
|
+
];
|
|
1049
|
+
for (const comp of otherUiImports) {
|
|
1050
|
+
const regex = new RegExp(`from\\s+"@/components/ui/${comp}"`, "g");
|
|
1051
|
+
content = content.replace(regex, `from "./${comp}"`);
|
|
1052
|
+
}
|
|
1053
|
+
return content.trimStart();
|
|
933
1054
|
}
|
|
934
1055
|
async add(components) {
|
|
1056
|
+
if (this.framework === "vite") {
|
|
1057
|
+
logger.info("Detected Vite project - using Vite-compatible components");
|
|
1058
|
+
} else if (this.framework === "nextjs") {
|
|
1059
|
+
logger.info("Detected Next.js project - using Next.js components");
|
|
1060
|
+
}
|
|
935
1061
|
components = await this.resolveComponents(components);
|
|
936
1062
|
const { valid, invalid } = this.validateComponents(components);
|
|
937
1063
|
if (invalid.length > 0) {
|
|
@@ -1023,9 +1149,23 @@ var ComponentAdder = class {
|
|
|
1023
1149
|
async installPackages(packages) {
|
|
1024
1150
|
const packageManager = getPackageManager(this.cwd);
|
|
1025
1151
|
const spinner = ora2("Installing dependencies...").start();
|
|
1026
|
-
|
|
1152
|
+
let installCmd;
|
|
1153
|
+
switch (packageManager) {
|
|
1154
|
+
case "deno":
|
|
1155
|
+
installCmd = ["add", "-A", ...packages];
|
|
1156
|
+
break;
|
|
1157
|
+
case "bun":
|
|
1158
|
+
installCmd = ["add", ...packages];
|
|
1159
|
+
break;
|
|
1160
|
+
case "pnpm":
|
|
1161
|
+
case "yarn":
|
|
1162
|
+
installCmd = ["add", ...packages];
|
|
1163
|
+
break;
|
|
1164
|
+
default:
|
|
1165
|
+
installCmd = ["install", ...packages];
|
|
1166
|
+
}
|
|
1027
1167
|
try {
|
|
1028
|
-
await execa2(packageManager,
|
|
1168
|
+
await execa2(packageManager, installCmd, {
|
|
1029
1169
|
cwd: this.cwd
|
|
1030
1170
|
});
|
|
1031
1171
|
spinner.succeed("Dependencies installed");
|
|
@@ -1104,12 +1244,26 @@ Please manually install: ${packages.join(" ")}`);
|
|
|
1104
1244
|
}
|
|
1105
1245
|
spinner.start("Adding components...");
|
|
1106
1246
|
}
|
|
1107
|
-
|
|
1247
|
+
let registryPath = path6.resolve(getRegistryPath(), comp.file);
|
|
1248
|
+
let content = "";
|
|
1249
|
+
if (this.framework === "vite") {
|
|
1250
|
+
const viteVariant = FRAMEWORK_SPECIFIC_COMPONENTS[name];
|
|
1251
|
+
if (viteVariant) {
|
|
1252
|
+
const vitePath = path6.resolve(getRegistryPath(), viteVariant);
|
|
1253
|
+
if (fs6.existsSync(vitePath)) {
|
|
1254
|
+
registryPath = vitePath;
|
|
1255
|
+
spinner.info(`Using Vite-specific ${fileName}`);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1108
1259
|
if (!fs6.existsSync(registryPath)) {
|
|
1109
1260
|
spinner.warn(`Registry file not found for ${name}: ${registryPath}`);
|
|
1110
1261
|
continue;
|
|
1111
1262
|
}
|
|
1112
|
-
|
|
1263
|
+
content = await fs6.readFile(registryPath, "utf-8");
|
|
1264
|
+
if (this.framework === "vite") {
|
|
1265
|
+
content = this.transformForVite(name, content);
|
|
1266
|
+
}
|
|
1113
1267
|
await fs6.writeFile(targetPath, content);
|
|
1114
1268
|
addedCount++;
|
|
1115
1269
|
if (components.length > 10) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@srcroot/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "A UI library with polymorphic, accessible React components",
|
|
5
5
|
"author": "Shifaul Islam",
|
|
6
6
|
"license": "MIT",
|
|
@@ -71,4 +71,4 @@
|
|
|
71
71
|
"optional": true
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useEffect } from "react"
|
|
2
|
+
import type { FC } from "react"
|
|
3
|
+
|
|
4
|
+
interface GoogleAnalyticsProps {
|
|
5
|
+
gaIds: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const GoogleAnalytics: FC<GoogleAnalyticsProps> = ({ gaIds }) => {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (gaIds.length === 0) return
|
|
11
|
+
|
|
12
|
+
const gtmScript = document.createElement("script")
|
|
13
|
+
gtmScript.async = true
|
|
14
|
+
gtmScript.src = `https://www.googletagmanager.com/gtag/js?id=${gaIds[0]}`
|
|
15
|
+
document.head.appendChild(gtmScript)
|
|
16
|
+
|
|
17
|
+
const inlineScript = document.createElement("script")
|
|
18
|
+
inlineScript.innerHTML = `
|
|
19
|
+
window.dataLayer = window.dataLayer || [];
|
|
20
|
+
function gtag(){dataLayer.push(arguments);}
|
|
21
|
+
gtag('js', new Date());
|
|
22
|
+
${gaIds.map((id) => `gtag('config', '${id}', { page_path: window.location.pathname });`).join("\n")}
|
|
23
|
+
`
|
|
24
|
+
document.head.appendChild(inlineScript)
|
|
25
|
+
|
|
26
|
+
return () => {
|
|
27
|
+
document.head.removeChild(gtmScript)
|
|
28
|
+
document.head.removeChild(inlineScript)
|
|
29
|
+
}
|
|
30
|
+
}, [gaIds])
|
|
31
|
+
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default GoogleAnalytics
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useEffect } from "react"
|
|
2
|
+
import type { FC, ReactNode } from "react"
|
|
3
|
+
|
|
4
|
+
interface GTMContainer {
|
|
5
|
+
gtmId: string;
|
|
6
|
+
tagServerUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface GoogleTagManagerProps {
|
|
10
|
+
containers: GTMContainer[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const GoogleTagManager: FC<GoogleTagManagerProps> = ({ containers }) => {
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const defaultServer = "https://www.googletagmanager.com"
|
|
16
|
+
|
|
17
|
+
const scriptsMap = containers.reduce((map, container) => {
|
|
18
|
+
const server = container.tagServerUrl || defaultServer;
|
|
19
|
+
if (!map.has(server)) {
|
|
20
|
+
map.set(server, []);
|
|
21
|
+
}
|
|
22
|
+
map.get(server)!.push(container.gtmId);
|
|
23
|
+
return map;
|
|
24
|
+
}, new Map<string, string[]>()); const scriptElements: HTMLScriptElement[] = []
|
|
25
|
+
|
|
26
|
+
Array.from(scriptsMap.entries()).forEach(([server, ids]) => {
|
|
27
|
+
ids.forEach((id) => {
|
|
28
|
+
const script = document.createElement("script")
|
|
29
|
+
script.innerHTML = `
|
|
30
|
+
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s);j.async=true;j.src="${server}/gtm.js?"+i;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','${id}');
|
|
31
|
+
`
|
|
32
|
+
script.id = `gtm-script-${server}-${id}`
|
|
33
|
+
document.head.appendChild(script)
|
|
34
|
+
scriptElements.push(script)
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
return () => {
|
|
39
|
+
scriptElements.forEach((script) => {
|
|
40
|
+
if (document.head.contains(script)) {
|
|
41
|
+
document.head.removeChild(script)
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
}, [containers])
|
|
46
|
+
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default GoogleTagManager
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useEffect } from "react"
|
|
2
|
+
import type { FC } from "react"
|
|
3
|
+
|
|
4
|
+
interface MetaPixelProps {
|
|
5
|
+
pixelIds: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const MetaPixel: FC<MetaPixelProps> = ({ pixelIds }) => {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (pixelIds.length === 0) return
|
|
11
|
+
|
|
12
|
+
const script = document.createElement("script")
|
|
13
|
+
script.innerHTML = `
|
|
14
|
+
!function(f,b,e,v,n,t,s)
|
|
15
|
+
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
|
16
|
+
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
|
|
17
|
+
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
|
|
18
|
+
n.queue=[];t=b.createElement(e);t.async=!0;
|
|
19
|
+
t.src=v;s=b.getElementsByTagName(e)[0];
|
|
20
|
+
s.parentNode.insertBefore(t,s)}(window, document,'script',
|
|
21
|
+
'https://connect.facebook.net/en_US/fbevents.js');
|
|
22
|
+
${pixelIds.map((id) => `fbq('init', '${id}');`).join("\n")}
|
|
23
|
+
fbq('track', 'PageView');
|
|
24
|
+
`
|
|
25
|
+
script.id = "fb-script-multi"
|
|
26
|
+
document.head.appendChild(script)
|
|
27
|
+
|
|
28
|
+
return () => {
|
|
29
|
+
if (document.head.contains(script)) {
|
|
30
|
+
document.head.removeChild(script)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}, [pixelIds])
|
|
34
|
+
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default MetaPixel
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useEffect } from "react"
|
|
2
|
+
import type { FC } from "react"
|
|
3
|
+
|
|
4
|
+
interface MicrosoftClarityProps {
|
|
5
|
+
clarityIds: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const MicrosoftClarity: FC<MicrosoftClarityProps> = ({ clarityIds }) => {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
clarityIds.forEach((id) => {
|
|
11
|
+
const script = document.createElement("script")
|
|
12
|
+
script.innerHTML = `
|
|
13
|
+
(function(c,l,a,r,i,t,y){
|
|
14
|
+
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
|
|
15
|
+
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
|
|
16
|
+
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
|
|
17
|
+
})(window, document, "clarity", "script", "${id}");
|
|
18
|
+
`
|
|
19
|
+
script.id = `microsoft-clarity-init-${id}`
|
|
20
|
+
document.head.appendChild(script)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
return () => {
|
|
24
|
+
clarityIds.forEach((id) => {
|
|
25
|
+
const script = document.getElementById(`microsoft-clarity-init-${id}`)
|
|
26
|
+
if (script && document.head.contains(script)) {
|
|
27
|
+
document.head.removeChild(script)
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
}, [clarityIds])
|
|
32
|
+
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default MicrosoftClarity
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useEffect } from "react"
|
|
2
|
+
import type { FC } from "react"
|
|
3
|
+
|
|
4
|
+
interface TikTokPixelProps {
|
|
5
|
+
pixelIds: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const TikTokPixel: FC<TikTokPixelProps> = ({ pixelIds }) => {
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const script = document.createElement("script")
|
|
11
|
+
script.innerHTML = `
|
|
12
|
+
!function (w, d, t) {
|
|
13
|
+
w.TiktokAnalyticsObject=t;var ttq=w[t]=w[t]||[];
|
|
14
|
+
ttq.methods=["page","track","identify","instances","debug","on","off","once","ready","alias","group","enableCookie","disableCookie","holdConsent","revokeConsent","grantConsent"],
|
|
15
|
+
ttq.setAndDefer=function(t,e){t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}};
|
|
16
|
+
for(var i=0;i<ttq.methods.length;i++)ttq.setAndDefer(ttq,ttq.methods[i]);
|
|
17
|
+
ttq.instance=function(t){for(var e=ttq._i[t]||[],n=0;n<ttq.methods.length;n++)ttq.setAndDefer(e,ttq.methods[n]);return e},
|
|
18
|
+
ttq.load=function(e,n){var r="https://analytics.tiktok.com/i18n/pixel/events.js";
|
|
19
|
+
ttq._i=ttq._i||{},ttq._i[e]=[],ttq._i[e]._u=r,ttq._t=ttq._t||{},ttq._t[e]=+new Date,ttq._o=ttq._o||{},ttq._o[e]=n||{};
|
|
20
|
+
var s=document.createElement("script");s.type="text/javascript",s.async=!0,s.src=r+"?sdkid="+e+"&lib="+t;
|
|
21
|
+
var p=document.getElementsByTagName("script")[0];p.parentNode.insertBefore(s,p)};
|
|
22
|
+
${pixelIds.map((id) => `ttq.load('${id}');`).join("\n")}
|
|
23
|
+
ttq.page();
|
|
24
|
+
}(window, document, 'ttq');
|
|
25
|
+
`
|
|
26
|
+
script.id = "tiktok-script-multi"
|
|
27
|
+
document.head.appendChild(script)
|
|
28
|
+
|
|
29
|
+
return () => {
|
|
30
|
+
if (document.head.contains(script)) {
|
|
31
|
+
document.head.removeChild(script)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}, [pixelIds])
|
|
35
|
+
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default TikTokPixel
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { FiSun, FiMoon, FiMonitor } from "react-icons/fi"
|
|
3
|
+
import { cn } from "../lib/utils"
|
|
4
|
+
|
|
5
|
+
interface ThemeSwitcherProps {
|
|
6
|
+
onThemeChange?: (theme: string) => void
|
|
7
|
+
className?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function ThemeSwitcher({ onThemeChange, className }: ThemeSwitcherProps) {
|
|
11
|
+
const [theme, setThemeState] = React.useState<string>("light")
|
|
12
|
+
const [mounted, setMounted] = React.useState(false)
|
|
13
|
+
|
|
14
|
+
React.useEffect(() => {
|
|
15
|
+
setMounted(true)
|
|
16
|
+
const stored = localStorage.getItem("theme") || "light"
|
|
17
|
+
setThemeState(stored)
|
|
18
|
+
}, [])
|
|
19
|
+
|
|
20
|
+
const setTheme = (newTheme: string) => {
|
|
21
|
+
setThemeState(newTheme)
|
|
22
|
+
localStorage.setItem("theme", newTheme)
|
|
23
|
+
if (newTheme === "dark") {
|
|
24
|
+
document.documentElement.classList.add("dark")
|
|
25
|
+
} else {
|
|
26
|
+
document.documentElement.classList.remove("dark")
|
|
27
|
+
}
|
|
28
|
+
onThemeChange?.(newTheme)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const toggleTheme = () => {
|
|
32
|
+
if (theme === "light") {
|
|
33
|
+
setTheme("dark")
|
|
34
|
+
} else if (theme === "dark") {
|
|
35
|
+
setTheme("system")
|
|
36
|
+
} else {
|
|
37
|
+
setTheme("light")
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const getIcon = () => {
|
|
42
|
+
if (!mounted) {
|
|
43
|
+
return <FiSun className="mr-2 h-4 w-4" />
|
|
44
|
+
}
|
|
45
|
+
if (theme === "system") {
|
|
46
|
+
return <FiMonitor className="mr-2 h-4 w-4" />
|
|
47
|
+
}
|
|
48
|
+
if (theme === "dark") {
|
|
49
|
+
return <FiMoon className="mr-2 h-4 w-4" />
|
|
50
|
+
}
|
|
51
|
+
return <FiSun className="mr-2 h-4 w-4" />
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const getLabel = () => {
|
|
55
|
+
if (!mounted) return "Theme"
|
|
56
|
+
if (theme === "system") return "System"
|
|
57
|
+
if (theme === "dark") return "Dark"
|
|
58
|
+
return "Light"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<button
|
|
63
|
+
onClick={toggleTheme}
|
|
64
|
+
className={cn(
|
|
65
|
+
"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-foreground hover:bg-accent hover:text-accent-foreground cursor-pointer",
|
|
66
|
+
className
|
|
67
|
+
)}
|
|
68
|
+
>
|
|
69
|
+
{getIcon()}
|
|
70
|
+
<span>Theme: {getLabel()}</span>
|
|
71
|
+
</button>
|
|
72
|
+
)
|
|
73
|
+
}
|