demo-dev2d-cli 1.0.0
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/bin/index.js +291 -0
- package/package.json +33 -0
- package/registry.json +9 -0
- package/templates/input.tsx +213 -0
- package/utils/cn.ts +6 -0
package/bin/index.js
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { Command } = require("commander");
|
|
4
|
+
const fs = require("fs-extra");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const babel = require("@babel/core");
|
|
7
|
+
const presetTypescript = require("@babel/preset-typescript");
|
|
8
|
+
const { execSync } = require("child_process");
|
|
9
|
+
const registry = require("../registry.json");
|
|
10
|
+
|
|
11
|
+
const program = new Command();
|
|
12
|
+
|
|
13
|
+
/* -----------------------------
|
|
14
|
+
INQUIRER (FIXED SAFE WRAPPER)
|
|
15
|
+
------------------------------*/
|
|
16
|
+
const prompt = async (questions) => {
|
|
17
|
+
const inquirer = await import("inquirer");
|
|
18
|
+
return inquirer.default.prompt(questions);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/* =============================
|
|
22
|
+
ADD COMMAND
|
|
23
|
+
============================= */
|
|
24
|
+
program
|
|
25
|
+
.command("add <name>")
|
|
26
|
+
.description("Add components or utilities")
|
|
27
|
+
.action(async (name) => {
|
|
28
|
+
try {
|
|
29
|
+
const cwd = process.cwd();
|
|
30
|
+
console.log("\nš Starting installation...\n");
|
|
31
|
+
|
|
32
|
+
const isTsProject = fs.existsSync(path.join(cwd, "tsconfig.json"));
|
|
33
|
+
|
|
34
|
+
let templatePath = "";
|
|
35
|
+
let outputDir = "";
|
|
36
|
+
let extension = "tsx";
|
|
37
|
+
|
|
38
|
+
/* ---------------- COMPONENTS ---------------- */
|
|
39
|
+
if (!name.startsWith("utils/")) {
|
|
40
|
+
templatePath = path.join(__dirname, `../templates/${name}.tsx`);
|
|
41
|
+
outputDir = path.join(cwd, "src/components/pejay-ui");
|
|
42
|
+
extension = "tsx";
|
|
43
|
+
} else {
|
|
44
|
+
|
|
45
|
+
/* ---------------- UTILITIES ---------------- */
|
|
46
|
+
const utilName = name.split("/")[1];
|
|
47
|
+
templatePath = path.join(__dirname, `../utils/${utilName}.ts`);
|
|
48
|
+
outputDir = path.join(cwd, "src/utils/pejay-ui");
|
|
49
|
+
extension = "ts";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!fs.existsSync(templatePath)) {
|
|
53
|
+
console.log(`ā "${name}" not found`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const code = await fs.readFile(templatePath, "utf8");
|
|
58
|
+
|
|
59
|
+
let finalCode = code;
|
|
60
|
+
let outputExt = extension;
|
|
61
|
+
|
|
62
|
+
/* ---------------- TRANSFORM ---------------- */
|
|
63
|
+
if (!isTsProject) {
|
|
64
|
+
const transformed = babel.transformSync(code, {
|
|
65
|
+
presets: [presetTypescript],
|
|
66
|
+
filename: `file.${extension}`,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (!transformed?.code) {
|
|
70
|
+
throw new Error("Babel transform failed");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
finalCode = transformed.code;
|
|
74
|
+
outputExt = extension === "tsx" ? "jsx" : "js";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await fs.ensureDir(outputDir);
|
|
78
|
+
|
|
79
|
+
const itemName = name.includes("/") ? name.split("/")[1] : name;
|
|
80
|
+
const outputPath = path.join(outputDir, `${itemName}.${outputExt}`);
|
|
81
|
+
|
|
82
|
+
await fs.writeFile(outputPath, finalCode);
|
|
83
|
+
console.log(`ā
Created ${itemName}.${outputExt}`);
|
|
84
|
+
|
|
85
|
+
/* ---------------- DEPENDENCIES ---------------- */
|
|
86
|
+
const itemRegistry = registry[name];
|
|
87
|
+
|
|
88
|
+
if (itemRegistry?.dependencies?.length) {
|
|
89
|
+
for (const dependency of itemRegistry.dependencies) {
|
|
90
|
+
const depName = dependency.includes("/")
|
|
91
|
+
? dependency.split("/")[1]
|
|
92
|
+
: dependency;
|
|
93
|
+
|
|
94
|
+
const depPath = path.join(__dirname, `../utils/${depName}.ts`);
|
|
95
|
+
|
|
96
|
+
if (!fs.existsSync(depPath)) continue;
|
|
97
|
+
|
|
98
|
+
let depCode = await fs.readFile(depPath, "utf8");
|
|
99
|
+
|
|
100
|
+
if (!isTsProject) {
|
|
101
|
+
const transformed = babel.transformSync(depCode, {
|
|
102
|
+
presets: [presetTypescript],
|
|
103
|
+
filename: `${depName}.ts`,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
depCode = transformed.code;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const utilDir = path.join(cwd, "src/utils/pejay-ui");
|
|
110
|
+
await fs.ensureDir(utilDir);
|
|
111
|
+
|
|
112
|
+
const ext = isTsProject ? "ts" : "js";
|
|
113
|
+
const outPath = path.join(utilDir, `${depName}.${ext}`);
|
|
114
|
+
|
|
115
|
+
if (!fs.existsSync(outPath)) {
|
|
116
|
+
await fs.writeFile(outPath, depCode);
|
|
117
|
+
console.log(`š¦ Added dependency ${depName}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* ---------------- STATE SAVE ---------------- */
|
|
123
|
+
const statePath = path.join(cwd, ".pejay-ui.json");
|
|
124
|
+
|
|
125
|
+
let state = {};
|
|
126
|
+
if (fs.existsSync(statePath)) {
|
|
127
|
+
state = JSON.parse(await fs.readFile(statePath, "utf8"));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
state.installed = state.installed || {};
|
|
131
|
+
|
|
132
|
+
state.installed[name] = {
|
|
133
|
+
files: [`src/components/pejay-ui/${itemName}.${outputExt}`],
|
|
134
|
+
uses:
|
|
135
|
+
itemRegistry?.dependencies?.map((d) =>
|
|
136
|
+
d.includes("/") ? d.split("/")[1] : d,
|
|
137
|
+
) || [],
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
await fs.writeFile(statePath, JSON.stringify(state, null, 2));
|
|
141
|
+
|
|
142
|
+
console.log(`\nš ${name} installed successfully\n`);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
console.error("\nā Add failed\n", err);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
/* =============================
|
|
149
|
+
REMOVE COMMAND (SAFE VERSION)
|
|
150
|
+
============================= */
|
|
151
|
+
program
|
|
152
|
+
.command("remove <name>")
|
|
153
|
+
.description("Remove components or utilities safely")
|
|
154
|
+
.action(async (name) => {
|
|
155
|
+
try {
|
|
156
|
+
const cwd = process.cwd();
|
|
157
|
+
console.log("\nš§¹ Starting removal...\n");
|
|
158
|
+
|
|
159
|
+
const itemRegistry = registry[name];
|
|
160
|
+
|
|
161
|
+
if (!itemRegistry) {
|
|
162
|
+
console.log(`ā "${name}" not found in registry`);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const isTsProject = fs.existsSync(path.join(cwd, "tsconfig.json"));
|
|
167
|
+
const itemName = name.includes("/") ? name.split("/")[1] : name;
|
|
168
|
+
|
|
169
|
+
const componentDir = path.join(cwd, "src/components/pejay-ui");
|
|
170
|
+
const utilsDir = path.join(cwd, "src/utils/pejay-ui");
|
|
171
|
+
|
|
172
|
+
let removedAny = false;
|
|
173
|
+
|
|
174
|
+
/* ---------------- REMOVE COMPONENT ---------------- */
|
|
175
|
+
const exts = isTsProject ? ["tsx", "ts"] : ["jsx", "js"];
|
|
176
|
+
|
|
177
|
+
for (const ext of exts) {
|
|
178
|
+
const file = path.join(componentDir, `${itemName}.${ext}`);
|
|
179
|
+
|
|
180
|
+
if (fs.existsSync(file)) {
|
|
181
|
+
await fs.remove(file);
|
|
182
|
+
console.log(`šļø Removed ${file}`);
|
|
183
|
+
removedAny = true;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* ---------------- LOAD STATE ---------------- */
|
|
188
|
+
const statePath = path.join(cwd, ".pejay-ui.json");
|
|
189
|
+
|
|
190
|
+
let state = {};
|
|
191
|
+
if (fs.existsSync(statePath)) {
|
|
192
|
+
state = JSON.parse(await fs.readFile(statePath, "utf8"));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
state.installed = state.installed || {};
|
|
196
|
+
|
|
197
|
+
/* ---------------- BUILD USAGE MAP ---------------- */
|
|
198
|
+
const usageMap = new Map();
|
|
199
|
+
|
|
200
|
+
for (const comp of Object.values(state.installed)) {
|
|
201
|
+
for (const u of comp.uses || []) {
|
|
202
|
+
usageMap.set(u, (usageMap.get(u) || 0) + 1);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* ---------------- SAFE UTILITY REMOVE ---------------- */
|
|
207
|
+
if (itemRegistry.dependencies?.length) {
|
|
208
|
+
for (const dep of itemRegistry.dependencies) {
|
|
209
|
+
const utilName = dep.includes("/") ? dep.split("/")[1] : dep;
|
|
210
|
+
|
|
211
|
+
const usage = usageMap.get(utilName) || 0;
|
|
212
|
+
|
|
213
|
+
if (usage > 1) {
|
|
214
|
+
console.log(`ā Skipping ${utilName} (used by other components)`);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const { confirm } = await prompt([
|
|
219
|
+
{
|
|
220
|
+
type: "confirm",
|
|
221
|
+
name: "confirm",
|
|
222
|
+
message: `Remove utility "${utilName}"?`,
|
|
223
|
+
default: false,
|
|
224
|
+
},
|
|
225
|
+
]);
|
|
226
|
+
|
|
227
|
+
if (!confirm) continue;
|
|
228
|
+
|
|
229
|
+
for (const ext of exts) {
|
|
230
|
+
const file = path.join(utilsDir, `${utilName}.${ext}`);
|
|
231
|
+
|
|
232
|
+
if (fs.existsSync(file)) {
|
|
233
|
+
await fs.remove(file);
|
|
234
|
+
console.log(`šļø Removed ${file}`);
|
|
235
|
+
removedAny = true;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/* ---------------- REMOVE PACKAGES ---------------- */
|
|
242
|
+
// Only collect packages from deps that NO other component still uses
|
|
243
|
+
const pkgs = [];
|
|
244
|
+
|
|
245
|
+
for (const dep of itemRegistry.dependencies || []) {
|
|
246
|
+
const utilName = dep.includes("/") ? dep.split("/")[1] : dep;
|
|
247
|
+
const usage = usageMap.get(utilName) || 0;
|
|
248
|
+
|
|
249
|
+
// usage > 1 means another component still needs this dep ā skip its packages
|
|
250
|
+
if (usage > 1) {
|
|
251
|
+
console.log(`ā Keeping packages for ${utilName} (used by other components)`);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const reg = registry[dep];
|
|
256
|
+
if (reg?.packages) pkgs.push(...reg.packages);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (pkgs.length) {
|
|
260
|
+
const unique = [...new Set(pkgs)];
|
|
261
|
+
|
|
262
|
+
const { ok } = await prompt([
|
|
263
|
+
{
|
|
264
|
+
type: "confirm",
|
|
265
|
+
name: "ok",
|
|
266
|
+
message: `Uninstall packages: ${unique.join(", ")}?`,
|
|
267
|
+
default: false,
|
|
268
|
+
},
|
|
269
|
+
]);
|
|
270
|
+
|
|
271
|
+
if (ok) {
|
|
272
|
+
execSync(`npm uninstall ${unique.join(" ")}`, {
|
|
273
|
+
cwd,
|
|
274
|
+
stdio: "inherit",
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* ---------------- CLEAN STATE ---------------- */
|
|
280
|
+
if (state.installed[name]) {
|
|
281
|
+
delete state.installed[name];
|
|
282
|
+
await fs.writeFile(statePath, JSON.stringify(state, null, 2));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
console.log(`\nš ${name} removed successfully\n`);
|
|
286
|
+
} catch (err) {
|
|
287
|
+
console.error("\nā Remove failed\n", err);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "demo-dev2d-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI to install reusable React components like shadcn/ui",
|
|
5
|
+
"main": "bin/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"demo-dev2d-cli": "./bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"templates",
|
|
12
|
+
"utils",
|
|
13
|
+
"registry.json"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [],
|
|
19
|
+
"author": "demo-auth",
|
|
20
|
+
"license": "ISC",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@babel/core": "^7.29.0",
|
|
23
|
+
"@babel/preset-react": "^7.28.5",
|
|
24
|
+
"@babel/preset-typescript": "^7.28.5",
|
|
25
|
+
"@types/react": "^19.2.14",
|
|
26
|
+
"clsx": "^2.1.1",
|
|
27
|
+
"commander": "^14.0.3",
|
|
28
|
+
"fs-extra": "^11.3.5",
|
|
29
|
+
"inquirer": "^13.4.3",
|
|
30
|
+
"react": "^19.2.6",
|
|
31
|
+
"tailwind-merge": "^3.6.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/registry.json
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { cn } from "../../utils/pejay-ui/cn";
|
|
3
|
+
|
|
4
|
+
interface InputProps extends Omit<
|
|
5
|
+
React.InputHTMLAttributes<HTMLInputElement>,
|
|
6
|
+
"prefix"
|
|
7
|
+
> {
|
|
8
|
+
label?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
error?: string;
|
|
11
|
+
icon?: React.ReactNode;
|
|
12
|
+
rightIcon?: React.ReactNode;
|
|
13
|
+
prefix?: React.ReactNode;
|
|
14
|
+
suffix?: React.ReactNode;
|
|
15
|
+
onRightIconClick?: (e: React.MouseEvent) => void;
|
|
16
|
+
variant?: "rounded" | "curved" | "square" | "floating";
|
|
17
|
+
labelDirection?:
|
|
18
|
+
| "label-left"
|
|
19
|
+
| "label-left-top"
|
|
20
|
+
| "label-left-bottom"
|
|
21
|
+
| "label-right"
|
|
22
|
+
| "label-right-top"
|
|
23
|
+
| "label-right-bottom"
|
|
24
|
+
| "label-top"
|
|
25
|
+
| "label-top-right"
|
|
26
|
+
| "label-top-center";
|
|
27
|
+
labelWidth?: string;
|
|
28
|
+
labelAlign?: "left" | "center" | "right";
|
|
29
|
+
labelGap?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
33
|
+
(
|
|
34
|
+
{
|
|
35
|
+
label,
|
|
36
|
+
description,
|
|
37
|
+
error,
|
|
38
|
+
icon,
|
|
39
|
+
rightIcon,
|
|
40
|
+
prefix,
|
|
41
|
+
suffix,
|
|
42
|
+
onRightIconClick,
|
|
43
|
+
variant = "rounded",
|
|
44
|
+
labelDirection = "label-top",
|
|
45
|
+
labelWidth = "w-32",
|
|
46
|
+
labelAlign,
|
|
47
|
+
labelGap = "gap-1.5",
|
|
48
|
+
className,
|
|
49
|
+
onFocus,
|
|
50
|
+
onBlur,
|
|
51
|
+
...props
|
|
52
|
+
},
|
|
53
|
+
ref,
|
|
54
|
+
) => {
|
|
55
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
56
|
+
|
|
57
|
+
const isFloating = variant === "floating";
|
|
58
|
+
const hasValue = props.value !== undefined && props.value !== "";
|
|
59
|
+
const isActive = isFocused || hasValue;
|
|
60
|
+
|
|
61
|
+
const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
|
|
62
|
+
setIsFocused(true);
|
|
63
|
+
onFocus?.(e);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
|
67
|
+
setIsFocused(false);
|
|
68
|
+
onBlur?.(e);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const isSideLabel =
|
|
72
|
+
labelDirection.startsWith("label-left") ||
|
|
73
|
+
labelDirection.startsWith("label-right");
|
|
74
|
+
const alignment =
|
|
75
|
+
labelAlign ||
|
|
76
|
+
(labelDirection.includes("-left")
|
|
77
|
+
? "left"
|
|
78
|
+
: labelDirection.includes("-right")
|
|
79
|
+
? "right"
|
|
80
|
+
: labelDirection.includes("-center")
|
|
81
|
+
? "center"
|
|
82
|
+
: "left");
|
|
83
|
+
|
|
84
|
+
const radiusClass =
|
|
85
|
+
variant === "square"
|
|
86
|
+
? "rounded-none"
|
|
87
|
+
: variant === "curved"
|
|
88
|
+
? "rounded-lg"
|
|
89
|
+
: "rounded-xl";
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div
|
|
93
|
+
className={cn(
|
|
94
|
+
"flex w-full",
|
|
95
|
+
labelGap,
|
|
96
|
+
isSideLabel ? "flex-row" : "flex-col",
|
|
97
|
+
className,
|
|
98
|
+
)}
|
|
99
|
+
>
|
|
100
|
+
{/* Standard Label (Non-Floating) */}
|
|
101
|
+
{label && !isFloating && (
|
|
102
|
+
<div
|
|
103
|
+
className={cn(
|
|
104
|
+
"flex flex-col",
|
|
105
|
+
isSideLabel ? "shrink-0" : "w-full",
|
|
106
|
+
labelDirection.endsWith("-top") && isSideLabel && "mt-1.5",
|
|
107
|
+
)}
|
|
108
|
+
>
|
|
109
|
+
<div
|
|
110
|
+
className={cn(
|
|
111
|
+
isSideLabel ? labelWidth : "w-full",
|
|
112
|
+
"flex flex-col",
|
|
113
|
+
alignment === "left" && "items-start text-left",
|
|
114
|
+
alignment === "right" && "items-end text-right",
|
|
115
|
+
alignment === "center" && "items-center text-center",
|
|
116
|
+
)}
|
|
117
|
+
>
|
|
118
|
+
<span className="text-sm font-semibold tracking-tight text-black dark:text-white uppercase">
|
|
119
|
+
{label}
|
|
120
|
+
</span>
|
|
121
|
+
{description && (
|
|
122
|
+
<span className="text-[11px] text-gray-400 font-medium mt-0.5">
|
|
123
|
+
{description}
|
|
124
|
+
</span>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
|
|
130
|
+
<div className="flex-1 relative group">
|
|
131
|
+
{/* Floating Label */}
|
|
132
|
+
{label && isFloating && (
|
|
133
|
+
<span
|
|
134
|
+
className={cn(
|
|
135
|
+
"absolute transition-all duration-200 pointer-events-none font-bold uppercase tracking-tight z-10 block truncate",
|
|
136
|
+
isActive
|
|
137
|
+
? "-top-2.5 left-3 right-auto text-[10px] bg-white dark:bg-black px-1.5 text-sky-500 max-w-[calc(100%-1.5rem)]"
|
|
138
|
+
: cn(
|
|
139
|
+
"top-1/2 -translate-y-1/2 text-[13px] text-gray-400",
|
|
140
|
+
icon ? "left-11 right-8" : "left-4 right-4",
|
|
141
|
+
),
|
|
142
|
+
)}
|
|
143
|
+
>
|
|
144
|
+
{label}
|
|
145
|
+
</span>
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
<div className="relative flex items-center">
|
|
149
|
+
{icon && (
|
|
150
|
+
<div
|
|
151
|
+
className={cn(
|
|
152
|
+
"absolute left-4 text-gray-400 transition-colors",
|
|
153
|
+
isFocused ? "text-black dark:text-white" : "",
|
|
154
|
+
)}
|
|
155
|
+
>
|
|
156
|
+
{icon}
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
<input
|
|
160
|
+
ref={ref}
|
|
161
|
+
{...props}
|
|
162
|
+
onFocus={handleFocus}
|
|
163
|
+
onBlur={handleBlur}
|
|
164
|
+
className={cn(
|
|
165
|
+
"w-full h-11 bg-white dark:bg-black border-2 text-[13px] font-semibold text-black dark:text-white transition-all duration-200 outline-none",
|
|
166
|
+
radiusClass,
|
|
167
|
+
icon || prefix ? "pl-11" : "px-4",
|
|
168
|
+
rightIcon || suffix ? "pr-11" : icon || prefix ? "" : "px-4",
|
|
169
|
+
isFocused
|
|
170
|
+
? "border-sky-500 ring-4 ring-sky-500/10"
|
|
171
|
+
: "border-gray-100 dark:border-gray-800 hover:border-gray-300 dark:hover:border-gray-600",
|
|
172
|
+
error ? "border-red-500 ring-4 ring-red-500/10" : "",
|
|
173
|
+
isFloating &&
|
|
174
|
+
"placeholder:opacity-0 focus:placeholder:opacity-100",
|
|
175
|
+
)}
|
|
176
|
+
/>
|
|
177
|
+
{prefix && (
|
|
178
|
+
<div className="absolute left-4 text-gray-400 font-semibold pointer-events-none">
|
|
179
|
+
{prefix}
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
{suffix && (
|
|
183
|
+
<div className="absolute right-4 text-gray-400 font-semibold pointer-events-none">
|
|
184
|
+
{suffix}
|
|
185
|
+
</div>
|
|
186
|
+
)}
|
|
187
|
+
{rightIcon && (
|
|
188
|
+
<div
|
|
189
|
+
onClick={onRightIconClick}
|
|
190
|
+
className={cn(
|
|
191
|
+
"absolute right-4 text-gray-400 transition-colors",
|
|
192
|
+
onRightIconClick
|
|
193
|
+
? "cursor-pointer hover:text-black dark:hover:text-white"
|
|
194
|
+
: "",
|
|
195
|
+
isFocused ? "text-black dark:text-white" : "",
|
|
196
|
+
)}
|
|
197
|
+
>
|
|
198
|
+
{rightIcon}
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
{error && (
|
|
203
|
+
<span className="text-[10px] font-bold text-red-500 mt-1.5 ml-1 block animate-in fade-in slide-in-from-top-1">
|
|
204
|
+
{error}
|
|
205
|
+
</span>
|
|
206
|
+
)}
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
);
|
|
210
|
+
},
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
Input.displayName = "Input";
|