🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

hookbase

Package Overview
Dependencies
Maintainers
1
Versions
83
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

hookbase - npm Package Compare versions

Comparing version
0.1.5
to
0.1.7
+30
templates/use-boolean.ts
import * as React from "react";
type UseBooleanReturn = {
value: boolean;
setValue: React.Dispatch<React.SetStateAction<boolean>>;
setTrue: () => void;
setFalse: () => void;
toggle: () => void;
};
export function useBoolean(defaultValue = false): UseBooleanReturn {
if (typeof defaultValue !== "boolean") {
throw new Error("defaultValue must be `true` or `false`");
}
const [value, setValue] = React.useState(defaultValue);
const setTrue = React.useCallback(() => {
setValue(true);
}, []);
const setFalse = React.useCallback(() => {
setValue(false);
}, []);
const toggle = React.useCallback(() => {
setValue((x) => !x);
}, []);
return { value, setValue, setTrue, setFalse, toggle };
}
import * as React from "react";
type CopyFn = (text: string) => Promise<boolean>;
export function useCopyToClipboard(delay = 2000): [CopyFn, boolean] {
const [isCopied, setIsCopied] = React.useState(false);
React.useEffect(() => {
if (!isCopied) return;
const timer = setTimeout(() => {
setIsCopied(false);
}, delay);
return () => clearTimeout(timer);
}, [isCopied, delay]);
const copy: CopyFn = React.useCallback(async (text) => {
if (!navigator?.clipboard) {
console.warn("Clipboard not supported");
return false;
}
try {
await navigator.clipboard.writeText(text);
setIsCopied(true);
return true;
} catch (error) {
console.warn("Copy failed", error);
return false;
}
}, []);
return [copy, isCopied];
}
import * as React from "react";
export function useDocumentTitle(title: string) {
React.useEffect(() => {
document.title = title;
}, [title]);
}
import * as React from "react";
export function useInterval(callback: () => void, delay: number | null) {
const savedCallback = React.useRef(callback);
React.useEffect(() => {
savedCallback.current = callback;
}, [callback]);
React.useEffect(() => {
if (delay === null || typeof delay !== "number") return;
const tick = () => savedCallback.current();
const id = setInterval(tick, delay);
return () => clearInterval(id);
}, [delay]);
}
import * as React from "react";
export const useIsomorphicLayoutEffect =
typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
import * as React from "react";
export function useUnmount(func: () => void) {
const funcRef = React.useRef(func);
funcRef.current = func;
React.useEffect(
() => () => {
funcRef.current();
},
[]
);
}
import * as React from "react";
type UseCounterReturn = {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
setCount: React.Dispatch<React.SetStateAction<number>>;
};
export function useCounter(initialValue?: number): UseCounterReturn {
const [count, setCount] = React.useState(initialValue ?? 0);
const increment = React.useCallback(() => {
setCount((x) => x + 1);
}, []);
const decrement = React.useCallback(() => {
setCount((x) => x - 1);
}, []);
const reset = React.useCallback(() => {
setCount(initialValue ?? 0);
}, [initialValue]);
return {
count,
increment,
decrement,
reset,
setCount,
};
}
import * as React from "react";
export type Position = {
x: number;
y: number;
elementX?: number;
elementY?: number;
elementPositionX?: number;
elementPositionY?: number;
};
export function useMousePosition<T extends HTMLElement>(): [Position, React.Ref<T>] {
const [state, setState] = React.useState<Position>({
x: 0,
y: 0,
});
const ref = React.useRef<T>(null);
React.useLayoutEffect(() => {
const handleMouseMove = (event: MouseEvent) => {
const newState: Position = {
x: event.pageX,
y: event.pageY,
};
if (ref.current?.nodeType === Node.ELEMENT_NODE) {
const { left, top } = ref.current.getBoundingClientRect();
const elementPositionX = left + window.scrollX;
const elementPositionY = top + window.scrollY;
const elementX = event.pageX - elementPositionX;
const elementY = event.pageY - elementPositionY;
newState.elementPositionX = elementPositionX;
newState.elementPositionY = elementPositionY;
newState.elementX = elementX;
newState.elementY = elementY;
}
setState((s) => ({
...s,
...newState,
}));
};
document.addEventListener("mousemove", handleMouseMove);
return () => document.removeEventListener("mousemove", handleMouseMove);
}, []);
return [state, ref];
}
+30
-112
#!/usr/bin/env node
import { Command } from "commander";
import fs from "fs/promises";

@@ -7,125 +6,44 @@ import path from "path";

const program = new Command();
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const toKebab = (name) =>
name
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
.replace(/[\s_]+/g, "-")
.toLowerCase();
async function copyDir(src, dest) {
await fs.mkdir(dest, { recursive: true });
const entries = await fs.readdir(src, { withFileTypes: true });
async function ensureDir(dir) {
await fs.mkdir(dir, { recursive: true });
}
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
async function fileExists(p) {
try {
await fs.access(p);
return true;
} catch {
return false;
if (entry.isDirectory()) {
await copyDir(srcPath, destPath);
} else {
await fs.copyFile(srcPath, destPath);
}
}
}
async function readTemplate(hookSlug) {
const base = path.join(__dirname, "../templates", hookSlug);
const ts = `${base}.ts`;
const tsx = `${base}.tsx`;
if (await fileExists(ts)) return { path: ts, ext: ".ts" };
if (await fileExists(tsx)) return { path: tsx, ext: ".tsx" };
throw new Error(`Template not found for "${hookSlug}" in /templates`);
}
async function main() {
try {
const projectRoot = process.cwd();
async function writeHook({ hookSlug, outDir, force }) {
const tpl = await readTemplate(hookSlug);
const code = await fs.readFile(tpl.path, "utf8");
// default to src/hooks if src exists, else hooks at root
const srcDir = path.join(projectRoot, "src");
const hasSrc = await fs
.access(srcDir)
.then(() => true)
.catch(() => false);
await ensureDir(outDir);
const outPath = path.join(outDir, `${hookSlug}${tpl.ext}`);
const dest = path.join(projectRoot, hasSrc ? "src/hooks" : "hooks");
const templatesDir = path.join(__dirname, "../templates");
if (!force && (await fileExists(outPath))) {
throw new Error(
`File exists: ${path.relative(
process.cwd(),
outPath
)} (use --force to overwrite)`
);
}
await fs.writeFile(outPath, code, "utf8");
return outPath;
}
await copyDir(templatesDir, dest);
async function updateBarrel(outDir, hookSlug) {
const barrel = path.join(outDir, "index.ts");
let content = "";
try {
content = await fs.readFile(barrel, "utf8");
} catch { }
const line = `export * from "./${hookSlug}";\n`;
if (!content.includes(line)) {
await fs.writeFile(barrel, content + line, "utf8");
return true;
console.log(`✅ Hooks copied to ${path.relative(projectRoot, dest)}`);
} catch (err) {
console.error("❌ Error copying hooks:", err.message);
process.exit(1);
}
return false;
}
// 🔹 Smart default: use src/hooks if src/ exists, else hooks/
async function getDefaultHooksDir() {
const root = process.cwd();
const src = path.join(root, "src");
if (await fileExists(src)) return "src/hooks";
return "hooks";
}
program
.name("hookbase")
.description("Install React hooks into your project’s /hooks folder")
.version("0.1.0");
program
.command("add <hook>")
.description("Add a hook by name (must exist in this package’s templates)")
.option("-d, --dir <path>", "output directory") // no default here
.option("-f, --force", "overwrite if file exists", false)
.option("--no-barrel", "do not update hooks/index.ts")
.action(async (hook, opts) => {
try {
const hookSlug = toKebab(hook);
const defaultDir = opts.dir || (await getDefaultHooksDir());
const outDir = path.resolve(process.cwd(), defaultDir);
const outPath = await writeHook({
hookSlug,
outDir,
force: opts.force,
});
console.log(
`✅ Added ${hookSlug} → ${path.relative(process.cwd(), outPath)}`
);
if (opts.barrel) {
const changed = await updateBarrel(outDir, hookSlug);
if (changed) console.log("📦 Updated hooks/index.ts");
}
} catch (err) {
console.error(`❌ ${err.message}`);
process.exitCode = 1;
}
});
program
.command("list")
.description("List available templates in this package")
.action(async () => {
const tdir = path.join(__dirname, "../templates");
const files = (await fs.readdir(tdir)).filter(
(f) => f.endsWith(".ts") || f.endsWith(".tsx")
);
if (files.length === 0) return console.log("No templates found.");
files
.map((f) => f.replace(/\.tsx?$/, ""))
.sort()
.forEach((name) => console.log(`- ${name}`));
});
program.parse();
main();
{
"name": "hookbase",
"version": "0.1.5",
"version": "0.1.7",
"type": "module",

@@ -13,5 +13,4 @@ "bin": {

],
"dependencies": {
"commander": "^12.0.0"
},
"preferGlobal": true,
"dependencies": {},
"devDependencies": {

@@ -18,0 +17,0 @@ "typescript": "^5.8.0",