diff --git a/apps/website/next.config.mjs b/apps/website/next.config.mjs
index d31280c..6dd6ed3 100755
--- a/apps/website/next.config.mjs
+++ b/apps/website/next.config.mjs
@@ -9,7 +9,7 @@ await jiti.import("./src/env.ts");
const nextConfig = {
experimental: {
// serverComponentsExternalPackages: ["shiki", "vscode-oniguruma"],
- // reactCompiler: true,
+ reactCompiler: true,
},
transpilePackages: ["shiki", "next-mdx-remote"],
images: {
diff --git a/apps/website/package.json b/apps/website/package.json
index e2e18b1..d4698c4 100644
--- a/apps/website/package.json
+++ b/apps/website/package.json
@@ -48,6 +48,7 @@
"@types/node": "20.12.7",
"@types/react": "19.0.1",
"@types/react-dom": "19.0.2",
+ "@types/web-bluetooth": "^0.0.20",
"@vitejs/plugin-react": "4.3.1",
"autoprefixer": "10.4.19",
"eslint": "9.0.0",
diff --git a/apps/website/src/app/layout.tsx b/apps/website/src/app/layout.tsx
index b25c104..a5e4d37 100644
--- a/apps/website/src/app/layout.tsx
+++ b/apps/website/src/app/layout.tsx
@@ -55,6 +55,12 @@ export default function RootLayout({
}) {
return (
+
+ {/* */}
+
{/* */}
diff --git a/apps/website/src/changelogs/2025-02-08.mdx b/apps/website/src/changelogs/2025-02-08.mdx
new file mode 100644
index 0000000..47df1ab
--- /dev/null
+++ b/apps/website/src/changelogs/2025-02-08.mdx
@@ -0,0 +1,9 @@
+---
+title: New hooks
+---
+
+- A new `useSessionStorage` hook has been added to manage session storage values.
+- A new `useMemory` hook has been added to retrieve memory values.
+- A new `useHistory` hook has been added to manage history values for advanced applications.
+- A new `useBluetooth` hook has been added to manage Bluetooth connections.
+- A new `useDropZone` hook has been added to manage drag-and-drop file uploads.
\ No newline at end of file
diff --git a/apps/website/src/changelogs/last-changelog-date.ts b/apps/website/src/changelogs/last-changelog-date.ts
index c6d5ea9..2f91198 100644
--- a/apps/website/src/changelogs/last-changelog-date.ts
+++ b/apps/website/src/changelogs/last-changelog-date.ts
@@ -1,2 +1,2 @@
// This file is generated by the generate-latest-changelog-date script
-export const lastChangelogDate = new Date("2025-01-19T23:00:00.000Z");
+export const lastChangelogDate = new Date("2025-02-07T23:00:00.000Z");
diff --git a/apps/website/src/lib/generated-package-check-list-to-install.ts b/apps/website/src/lib/generated-package-check-list-to-install.ts
index d6924ca..ddb94f1 100644
--- a/apps/website/src/lib/generated-package-check-list-to-install.ts
+++ b/apps/website/src/lib/generated-package-check-list-to-install.ts
@@ -159,6 +159,10 @@ export const packageCheckListToInstall: PackageToInstallType[] = [
find: [`from "@types/react-dom"`],
packageName: "@types/react-dom",
},
+ {
+ find: [`from "@types/web-bluetooth"`],
+ packageName: "@types/web-bluetooth",
+ },
{
find: [`from "@vitejs/plugin-react"`],
packageName: "@vitejs/plugin-react",
diff --git a/packages/ui/cuicui/application-ui/battery/battery-indicator/battery-indicator.tsx b/packages/ui/cuicui/application-ui/battery/battery-indicator/battery-indicator.tsx
index d0866b5..30d677c 100644
--- a/packages/ui/cuicui/application-ui/battery/battery-indicator/battery-indicator.tsx
+++ b/packages/ui/cuicui/application-ui/battery/battery-indicator/battery-indicator.tsx
@@ -7,7 +7,7 @@ import {
BatteryWarningIcon,
InfinityIcon,
} from "lucide-react";
-import type React from "react";
+
import { cn } from "@/cuicui/utils/cn";
type BatteryInfoProps = {
@@ -18,13 +18,13 @@ type BatteryInfoProps = {
className?: string;
};
-export const BatteryIndicator: React.FC = ({
+export const BatteryIndicator = ({
level,
isCharging,
chargingTime,
dischargingTime,
className,
-}) => {
+}: BatteryInfoProps) => {
const getBatteryIcon = (level: number | null, isCharging: boolean | null) => {
if (level === null) {
return ;
diff --git a/packages/ui/cuicui/hooks/use-bluetooth/category.ts b/packages/ui/cuicui/hooks/use-bluetooth/category.ts
new file mode 100644
index 0000000..b0b97f3
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-bluetooth/category.ts
@@ -0,0 +1,11 @@
+import type { CategoryMetaType } from "@/lib/types/component";
+import { BluetoothIcon } from "lucide-react";
+
+export const category: CategoryMetaType = {
+ name: "Use Bluetooth",
+ description: "A hook to use bluetooth",
+ icon: BluetoothIcon,
+ latestUpdateDate: new Date("2025-02-08"),
+};
+
+export default category;
diff --git a/packages/ui/cuicui/hooks/use-bluetooth/hook/component.ts b/packages/ui/cuicui/hooks/use-bluetooth/hook/component.ts
new file mode 100644
index 0000000..6ad7ab0
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-bluetooth/hook/component.ts
@@ -0,0 +1,8 @@
+import type { ComponentMetaType } from "@/lib/types/component";
+
+export const component: ComponentMetaType = {
+ name: "Use Bluetooth",
+ description: "A hook to use bluetooth",
+};
+
+export default component;
diff --git a/packages/ui/cuicui/hooks/use-bluetooth/hook/use-bluetooh.variant.tsx b/packages/ui/cuicui/hooks/use-bluetooth/hook/use-bluetooh.variant.tsx
new file mode 100644
index 0000000..b0e5531
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-bluetooth/hook/use-bluetooh.variant.tsx
@@ -0,0 +1,32 @@
+"use client";
+
+import { useBluetooth } from "@/cuicui/hooks/use-bluetooth";
+
+function BluetoothComponent() {
+ const { isSupported, isConnected, device, requestDevice, server, error } =
+ useBluetooth({
+ acceptAllDevices: true,
+ optionalServices: ["battery_service"],
+ });
+
+ if (!isSupported) {
+ return Bluetooth is not supported
;
+ }
+
+ return (
+
+
+ {isConnected &&
Connected to: {device?.name}
}
+ {(error as JSON) && (
+ <>
+
Error:
+
{JSON.stringify(error, null, 2)}
+ >
+ )}
+
+ );
+}
+
+export default BluetoothComponent;
diff --git a/packages/ui/cuicui/hooks/use-bluetooth/index.ts b/packages/ui/cuicui/hooks/use-bluetooth/index.ts
new file mode 100644
index 0000000..746926d
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-bluetooth/index.ts
@@ -0,0 +1,122 @@
+"use client";
+//This requires @types/web-bluetooth package to be installed
+import { useState, useEffect, useCallback } from "react";
+
+interface UseBluetoothRequestDeviceOptions {
+ filters?: BluetoothLEScanFilter[];
+ optionalServices?: BluetoothServiceUUID[];
+}
+
+interface UseBluetoothOptions extends UseBluetoothRequestDeviceOptions {
+ acceptAllDevices?: boolean;
+ navigator?: Navigator;
+}
+
+export function useBluetooth(options?: UseBluetoothOptions) {
+ const windowVar = typeof window !== "undefined" ? window : undefined;
+ const {
+ acceptAllDevices = false,
+ filters = undefined,
+ optionalServices = undefined,
+ navigator = windowVar?.navigator,
+ } = options || {};
+
+ const [device, setDevice] = useState();
+ const [server, setServer] = useState();
+ const [isConnected, setIsConnected] = useState(false);
+ const [error, setError] = useState(null);
+
+ const isSupported = Boolean(navigator && "bluetooth" in navigator);
+
+ const reset = useCallback(() => {
+ setIsConnected(false);
+ setDevice(undefined);
+ setServer(undefined);
+ }, []);
+
+ const connectToBluetoothGATTServer = useCallback(async () => {
+ setError(null);
+
+ if (device?.gatt) {
+ try {
+ const newServer = await device.gatt.connect();
+ setServer(newServer);
+ setIsConnected(newServer.connected);
+ } catch (err) {
+ setError(err);
+ }
+ }
+ }, [device]);
+
+ const requestDevice = async () => {
+ if (!isSupported) {
+ return;
+ }
+
+ setError(null);
+ let effectiveAcceptAllDevices = acceptAllDevices;
+
+ if (filters && filters.length > 0) {
+ effectiveAcceptAllDevices = false;
+ }
+
+ try {
+ const newDevice = await navigator?.bluetooth.requestDevice({
+ acceptAllDevices: effectiveAcceptAllDevices,
+ filters,
+ optionalServices,
+ });
+ setDevice(newDevice);
+ } catch (err) {
+ setError(err);
+ }
+ };
+
+ useEffect(() => {
+ if (device) {
+ connectToBluetoothGATTServer();
+
+ // Add disconnect listener
+ const handleDisconnect = () => reset();
+ device.addEventListener("gattserverdisconnected", handleDisconnect);
+
+ return () => {
+ device.removeEventListener("gattserverdisconnected", handleDisconnect);
+ };
+ }
+ }, [device, connectToBluetoothGATTServer, reset]);
+
+ // Connect on mount if device exists
+ useEffect(() => {
+ if (device) {
+ device.gatt?.connect();
+ }
+ }, [device]);
+
+ // Disconnect on unmount
+ useEffect(() => {
+ return () => {
+ if (device) {
+ device.gatt?.disconnect();
+ }
+ };
+ }, [device]);
+
+ return {
+ isSupported,
+ isConnected,
+ device,
+ requestDevice,
+ server,
+ error,
+ };
+}
+
+export interface UseBluetoothReturn {
+ isSupported: boolean;
+ isConnected: boolean;
+ device: BluetoothDevice | undefined;
+ requestDevice: () => Promise;
+ server: BluetoothRemoteGATTServer | undefined;
+ error: unknown | null;
+}
diff --git a/packages/ui/cuicui/hooks/use-draggable/category.ts b/packages/ui/cuicui/hooks/use-draggable/category.ts
new file mode 100644
index 0000000..434840f
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-draggable/category.ts
@@ -0,0 +1,12 @@
+import type { CategoryMetaType } from "@/lib/types/component";
+import { HandIcon } from "lucide-react";
+
+export const category: CategoryMetaType = {
+ name: "Use Draggable",
+ description: "A hook to use draggable",
+ icon: HandIcon,
+ latestUpdateDate: new Date("2025-02-08"),
+ isComingSoon: true,
+};
+
+export default category;
diff --git a/packages/ui/cuicui/hooks/use-draggable/hook/component.ts b/packages/ui/cuicui/hooks/use-draggable/hook/component.ts
new file mode 100644
index 0000000..02d8af6
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-draggable/hook/component.ts
@@ -0,0 +1,8 @@
+import type { ComponentMetaType } from "@/lib/types/component";
+
+export const component: ComponentMetaType = {
+ name: "Use Draggable",
+ description: "A hook to use draggable",
+};
+
+export default component;
diff --git a/packages/ui/cuicui/hooks/use-draggable/hook/use-draggable.variant.tsx b/packages/ui/cuicui/hooks/use-draggable/hook/use-draggable.variant.tsx
new file mode 100644
index 0000000..241beee
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-draggable/hook/use-draggable.variant.tsx
@@ -0,0 +1,9 @@
+// Coming soon
+
+export default function useDraggableDemo() {
+ return(
+
+ Coming soon
+
+ );
+}
diff --git a/packages/ui/cuicui/hooks/use-draggable/index.ts b/packages/ui/cuicui/hooks/use-draggable/index.ts
new file mode 100644
index 0000000..f66196a
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-draggable/index.ts
@@ -0,0 +1 @@
+//Coming soon
diff --git a/packages/ui/cuicui/hooks/use-drop-zone/category.ts b/packages/ui/cuicui/hooks/use-drop-zone/category.ts
new file mode 100644
index 0000000..fb9bb37
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-drop-zone/category.ts
@@ -0,0 +1,11 @@
+import type { CategoryMetaType } from "@/lib/types/component";
+import { DownloadIcon } from "lucide-react";
+
+export const category: CategoryMetaType = {
+ name: "Use DropZone",
+ description: "A hook to handle file drop events",
+ icon: DownloadIcon,
+ latestUpdateDate: new Date("2025-02-08"),
+};
+
+export default category;
diff --git a/packages/ui/cuicui/hooks/use-drop-zone/hook/component.ts b/packages/ui/cuicui/hooks/use-drop-zone/hook/component.ts
new file mode 100644
index 0000000..b078e75
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-drop-zone/hook/component.ts
@@ -0,0 +1,8 @@
+import type { ComponentMetaType } from "@/lib/types/component";
+
+export const component: ComponentMetaType = {
+ name: "Use DropZone",
+ description: "A hook to handle file drop events",
+};
+
+export default component;
diff --git a/packages/ui/cuicui/hooks/use-drop-zone/hook/drop-zone.variant.tsx b/packages/ui/cuicui/hooks/use-drop-zone/hook/drop-zone.variant.tsx
new file mode 100644
index 0000000..542a913
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-drop-zone/hook/drop-zone.variant.tsx
@@ -0,0 +1,37 @@
+"use client";
+import { useDropZone } from "@/cuicui/hooks/use-drop-zone";
+import { cn } from "@/cuicui/utils/cn";
+import { useRef } from "react";
+
+export const DropZoneComponent = () => {
+ const dropZoneRef = useRef(null);
+ const { files, isOverDropZone } = useDropZone(dropZoneRef, {
+ onDrop: (files) => {
+ console.log("Dropped files:", files);
+ },
+ });
+
+ return (
+
+
+ Drop files here
+ {files &&
Dropped {files.length} files
}
+
+
Here is the filenames dropped:
+
+ {files?.map((file) => (
+ - {file.name}
+ ))}
+
+
+ );
+};
+export default DropZoneComponent;
diff --git a/packages/ui/cuicui/hooks/use-drop-zone/index.ts b/packages/ui/cuicui/hooks/use-drop-zone/index.ts
new file mode 100644
index 0000000..fa0668a
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-drop-zone/index.ts
@@ -0,0 +1,166 @@
+import { useEventListener } from "@/cuicui/hooks/use-event-listener";
+import { useRef, useState, type RefObject } from "react";
+
+export interface UseDropZoneOptions {
+ dataTypes?: string[] | ((types: readonly string[]) => boolean);
+ onDrop?: (files: File[] | null, event: DragEvent) => void;
+ onEnter?: (files: File[] | null, event: DragEvent) => void;
+ onLeave?: (files: File[] | null, event: DragEvent) => void;
+ onOver?: (files: File[] | null, event: DragEvent) => void;
+ multiple?: boolean;
+ preventDefaultForUnhandled?: boolean;
+}
+
+export interface UseDropZoneReturn {
+ files: File[] | null;
+ isOverDropZone: boolean;
+}
+
+const safariRegex = /^(?:(?!chrome|android).)*safari/i;
+
+export function useDropZone(
+ target: RefObject,
+ options: UseDropZoneOptions | UseDropZoneOptions["onDrop"] = {},
+): UseDropZoneReturn {
+ const [isOverDropZone, setIsOverDropZone] = useState(false);
+ const [files, setFiles] = useState(null);
+ const counterRef = useRef(0);
+ const isValidRef = useRef(true);
+
+ const _options =
+ typeof options === "function" ? { onDrop: options } : options;
+ const multiple = _options.multiple ?? true;
+ const preventDefaultForUnhandled =
+ _options.preventDefaultForUnhandled ?? false;
+
+ const getFiles = (event: DragEvent) => {
+ const list = Array.from(event.dataTransfer?.files ?? []);
+ if (list.length === 0) {
+ return null;
+ }
+ return multiple ? list : [list[0]];
+ };
+
+ const checkDataTypes = (types: string[]) => {
+ const dataTypes = _options.dataTypes;
+
+ if (typeof dataTypes === "function") {
+ return dataTypes(types);
+ }
+
+ // biome-ignore lint/style/useExplicitLengthCheck:
+ if (!dataTypes?.length) {
+ return true;
+ }
+
+ if (types.length === 0) {
+ return false;
+ }
+
+ return types.every((type) =>
+ dataTypes.some((allowedType) => type.includes(allowedType)),
+ );
+ };
+
+ const checkValidity = (items: DataTransferItemList) => {
+ const types = Array.from(items ?? []).map((item) => item.type);
+ const dataTypesValid = checkDataTypes(types);
+ const multipleFilesValid = multiple || items.length <= 1;
+ return dataTypesValid && multipleFilesValid;
+ };
+
+ const isSafari = () =>
+ safariRegex.test(navigator.userAgent) && !("chrome" in window);
+
+ const handleDragEvent = (
+ event: DragEvent,
+ eventType: "enter" | "over" | "leave" | "drop",
+ ) => {
+ const dataTransferItemList = event.dataTransfer?.items;
+ isValidRef.current =
+ (dataTransferItemList && checkValidity(dataTransferItemList)) ?? false;
+
+ if (preventDefaultForUnhandled) {
+ event.preventDefault();
+ }
+
+ if (!(isSafari() || isValidRef.current)) {
+ if (event.dataTransfer) {
+ event.dataTransfer.dropEffect = "none";
+ }
+ return;
+ }
+
+ event.preventDefault();
+ if (event.dataTransfer) {
+ event.dataTransfer.dropEffect = "copy";
+ }
+
+ const currentFiles = getFiles(event);
+
+ switch (eventType) {
+ case "enter": {
+ counterRef.current += 1;
+ setIsOverDropZone(true);
+ _options.onEnter?.(null, event);
+ break;
+ }
+ case "over":
+ _options.onOver?.(null, event);
+ break;
+ case "leave": {
+ counterRef.current -= 1;
+ if (counterRef.current === 0) {
+ setIsOverDropZone(false);
+ }
+ _options.onLeave?.(null, event);
+ break;
+ }
+ case "drop": {
+ counterRef.current = 0;
+ setIsOverDropZone(false);
+ if (isValidRef.current) {
+ setFiles(currentFiles);
+ _options.onDrop?.(currentFiles, event);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ };
+
+ useEventListener(
+ "dragenter",
+ (dragEvent) => {
+ handleDragEvent(dragEvent, "enter");
+ },
+ target,
+ );
+ useEventListener(
+ "dragover",
+ (dragEvent) => {
+ handleDragEvent(dragEvent, "over");
+ },
+ target,
+ );
+ useEventListener(
+ "dragleave",
+ (dragEvent) => {
+ handleDragEvent(dragEvent, "leave");
+ },
+ target,
+ );
+ useEventListener(
+ "drop",
+ (dragEvent) => {
+ handleDragEvent(dragEvent, "drop");
+ },
+ target,
+ );
+
+ return {
+ files,
+ isOverDropZone,
+ };
+}
diff --git a/packages/ui/cuicui/hooks/use-event-listener/index.ts b/packages/ui/cuicui/hooks/use-event-listener/index.ts
index cfa6a12..ededfbe 100644
--- a/packages/ui/cuicui/hooks/use-event-listener/index.ts
+++ b/packages/ui/cuicui/hooks/use-event-listener/index.ts
@@ -31,7 +31,7 @@ function useEventListener<
handler:
| ((event: HTMLElementEventMap[K]) => void)
| ((event: SVGElementEventMap[K]) => void),
- element: RefObject,
+ element: RefObject,
options?: boolean | AddEventListenerOptions,
): void;
diff --git a/packages/ui/cuicui/hooks/use-history/category.ts b/packages/ui/cuicui/hooks/use-history/category.ts
new file mode 100644
index 0000000..fbe1542
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-history/category.ts
@@ -0,0 +1,12 @@
+import type { CategoryMetaType } from "@/lib/types/component";
+import { HistoryIcon } from "lucide-react";
+
+export const category: CategoryMetaType = {
+ name: "Use History",
+ description:
+ "A hook to save and manage history with undo and redo functionalities",
+ icon: HistoryIcon,
+ latestUpdateDate: new Date("2025-02-08"),
+};
+
+export default category;
diff --git a/packages/ui/cuicui/hooks/use-history/hook/component.ts b/packages/ui/cuicui/hooks/use-history/hook/component.ts
new file mode 100644
index 0000000..92f8ba9
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-history/hook/component.ts
@@ -0,0 +1,9 @@
+import type { ComponentMetaType } from "@/lib/types/component";
+
+export const component: ComponentMetaType = {
+ name: "Use History",
+ description:
+ "A hook to save and manage history with undo and redo functionalities",
+};
+
+export default component;
diff --git a/packages/ui/cuicui/hooks/use-history/hook/use-history.variant.tsx b/packages/ui/cuicui/hooks/use-history/hook/use-history.variant.tsx
new file mode 100644
index 0000000..8941872
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-history/hook/use-history.variant.tsx
@@ -0,0 +1,84 @@
+"use client";
+import useHistory from "@/cuicui/hooks/use-history";
+import { useState, useCallback } from "react";
+
+function useCounter(initialValue = 0) {
+ const [count, setCount] = useState(initialValue);
+
+ const inc = useCallback(() => setCount((c) => c + 1), []);
+ const dec = useCallback(() => setCount((c) => c - 1), []);
+
+ return { count, setCount, inc, dec };
+}
+
+function Demo() {
+ const { count, setCount, inc, dec } = useCounter(0);
+ const { history, addToHistory, undo, redo, canUndo, canRedo } = useHistory(
+ count,
+ { capacity: 10 },
+ );
+
+ const handleIncrement = () => {
+ inc();
+ addToHistory(count + 1);
+ };
+
+ const handleDecrement = () => {
+ dec();
+ addToHistory(count - 1);
+ };
+
+ const handleUndo = () => {
+ const previousValue = undo();
+ setCount(previousValue);
+ };
+
+ const handleRedo = () => {
+ const nextValue = redo();
+ setCount(nextValue);
+ };
+
+ return (
+
+
Count: {count}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ History (limited to 10 records for demo)
+
+
+ {history.map((entry) => (
+
+
+ {new Date(entry.timestamp).toString()}
+
+ {`{ value: ${entry.snapshot} }`}
+
+ ))}
+
+
+
+ );
+}
+
+export default Demo;
diff --git a/packages/ui/cuicui/hooks/use-history/index.ts b/packages/ui/cuicui/hooks/use-history/index.ts
new file mode 100644
index 0000000..91f4ced
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-history/index.ts
@@ -0,0 +1,55 @@
+import { useCallback, useState } from "react";
+
+interface HistoryEntry {
+ timestamp: number;
+ snapshot: number;
+}
+
+export function useHistory(value: number, options: { capacity?: number } = {}) {
+ const [history, setHistory] = useState([]);
+ const [currentIndex, setCurrentIndex] = useState(-1);
+
+ const { capacity = 10 } = options;
+
+ const addToHistory = useCallback(
+ (newValue: number) => {
+ setHistory((prev) => {
+ const newHistory = [
+ ...prev.slice(0, currentIndex + 1),
+ { timestamp: Date.now(), snapshot: newValue },
+ ].slice(-capacity);
+
+ setCurrentIndex(newHistory.length - 1);
+ return newHistory;
+ });
+ },
+ [currentIndex, capacity],
+ );
+
+ const undo = useCallback(() => {
+ if (currentIndex > 0) {
+ setCurrentIndex((i) => i - 1);
+ return history[currentIndex - 1].snapshot;
+ }
+ return value;
+ }, [currentIndex, history, value]);
+
+ const redo = useCallback(() => {
+ if (currentIndex < history.length - 1) {
+ setCurrentIndex((i) => i + 1);
+ return history[currentIndex + 1].snapshot;
+ }
+ return value;
+ }, [currentIndex, history, value]);
+
+ return {
+ history,
+ addToHistory,
+ undo,
+ redo,
+ canUndo: currentIndex > 0,
+ canRedo: currentIndex < history.length - 1,
+ };
+}
+
+export default useHistory;
diff --git a/packages/ui/cuicui/hooks/use-memory/category.ts b/packages/ui/cuicui/hooks/use-memory/category.ts
new file mode 100644
index 0000000..9c74d0d
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-memory/category.ts
@@ -0,0 +1,11 @@
+import type { CategoryMetaType } from "@/lib/types/component";
+import { HardDriveIcon } from "lucide-react";
+
+export const category: CategoryMetaType = {
+ name: "Use Memory",
+ description: "A hook to retrieve memory information",
+ icon: HardDriveIcon,
+ latestUpdateDate: new Date("2025-02-08"),
+};
+
+export default category;
diff --git a/packages/ui/cuicui/hooks/use-memory/hook/component.ts b/packages/ui/cuicui/hooks/use-memory/hook/component.ts
new file mode 100644
index 0000000..609aa9b
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-memory/hook/component.ts
@@ -0,0 +1,8 @@
+import type { ComponentMetaType } from "@/lib/types/component";
+
+export const component: ComponentMetaType = {
+ name: "Use Memory",
+ description: "A hook to retrieve memory information",
+};
+
+export default component;
diff --git a/packages/ui/cuicui/hooks/use-memory/hook/use-memory.variant.tsx b/packages/ui/cuicui/hooks/use-memory/hook/use-memory.variant.tsx
new file mode 100644
index 0000000..4aea142
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-memory/hook/use-memory.variant.tsx
@@ -0,0 +1,34 @@
+"use client"
+import useMemory from '@/cuicui/hooks/use-memory';
+
+function MemoryDemo() {
+ const { isSupported, memory } = useMemory({ interval: 1000, immediate: true });
+
+ const size = (v: number) => {
+ const kb = v / 1024 / 1024;
+ return `${kb.toFixed(2)} MB`;
+ };
+
+ if (!isSupported || !memory) {
+ return (
+
+ Your browser does not support performance memory API
+
+ );
+ }
+
+ return (
+
+
Used
+
{size(memory.usedJSHeapSize)}
+
+
Allocated
+
{size(memory.totalJSHeapSize)}
+
+
Limit
+
{size(memory.jsHeapSizeLimit)}
+
+ );
+}
+
+export default MemoryDemo;
diff --git a/packages/ui/cuicui/hooks/use-memory/index.ts b/packages/ui/cuicui/hooks/use-memory/index.ts
new file mode 100644
index 0000000..fbff3c8
--- /dev/null
+++ b/packages/ui/cuicui/hooks/use-memory/index.ts
@@ -0,0 +1,67 @@
+"use client"
+import { useState, useEffect, useCallback } from "react";
+
+/**
+ * Performance.memory
+ */
+export interface MemoryInfo {
+ readonly jsHeapSizeLimit: number;
+ readonly totalJSHeapSize: number;
+ readonly usedJSHeapSize: number;
+ [Symbol.toStringTag]: "MemoryInfo";
+}
+
+export interface UseMemoryOptions {
+ interval?: number;
+ immediate?: boolean;
+ immediateCallback?: boolean;
+}
+
+type PerformanceMemory = Performance & {
+ memory: MemoryInfo;
+};
+
+/**
+ * Custom hook for Memory Info.
+ */
+export function useMemory(options: UseMemoryOptions = {}) {
+ const [memory, setMemory] = useState();
+ const [isSupported, setIsSupported] = useState(false);
+
+ // Check if performance.memory is supported
+ useEffect(() => {
+ setIsSupported(
+ typeof performance !== "undefined" && "memory" in performance,
+ );
+ }, []);
+
+ // Memory update function
+ const updateMemory = useCallback(() => {
+ if (isSupported) {
+ setMemory((performance as PerformanceMemory).memory);
+ }
+ }, [isSupported]);
+
+ // Set up interval
+ useEffect(() => {
+ if (!isSupported) return;
+
+ const { interval = 1000, immediate = false } = options;
+
+ // Initial call if immediate is true
+ if (immediate) {
+ updateMemory();
+ }
+
+ const intervalId = setInterval(updateMemory, interval);
+
+ // Cleanup
+ return () => {
+ clearInterval(intervalId);
+ };
+ }, [isSupported, options, updateMemory]);
+
+ return { isSupported, memory };
+}
+
+export default useMemory;
diff --git a/packages/ui/cuicui/hooks/use-session-storage/category.ts b/packages/ui/cuicui/hooks/use-session-storage/category.ts
index f7f7bd1..ab59f77 100644
--- a/packages/ui/cuicui/hooks/use-session-storage/category.ts
+++ b/packages/ui/cuicui/hooks/use-session-storage/category.ts
@@ -6,9 +6,8 @@ import type { CategoryMetaType } from "@/lib/types/component";
export const useSessionStorageCategory: CategoryMetaType = {
name: "Use Session Storage",
description: "A hook that allows you to manage session storage values",
- latestUpdateDate: new Date("2024-08-28"),
+ latestUpdateDate: new Date("2025-02-08"),
icon: ViewIcon,
- isComingSoon: true,
};
export default useSessionStorageCategory;
diff --git a/packages/ui/cuicui/hooks/use-session-storage/hook/session-storage.variant.tsx b/packages/ui/cuicui/hooks/use-session-storage/hook/session-storage.variant.tsx
index 0bde142..417fd00 100644
--- a/packages/ui/cuicui/hooks/use-session-storage/hook/session-storage.variant.tsx
+++ b/packages/ui/cuicui/hooks/use-session-storage/hook/session-storage.variant.tsx
@@ -1,6 +1,39 @@
+"use client";
+import { useSessionStorage } from "@/cuicui/hooks/use-session-storage";
+
// Coming soon
export const useSpeechToTextPreview = () => {
- return Coming soon
;
+ const [value, setValue, removeValue] = useSessionStorage("test-key", 0);
+
+ return (
+
+
Count: {value}
+
+
+
+
+ );
};
export default useSpeechToTextPreview;
diff --git a/packages/ui/cuicui/hooks/use-session-storage/index.ts b/packages/ui/cuicui/hooks/use-session-storage/index.ts
index 9bb4e5a..b78ed4d 100644
--- a/packages/ui/cuicui/hooks/use-session-storage/index.ts
+++ b/packages/ui/cuicui/hooks/use-session-storage/index.ts
@@ -1 +1,169 @@
-// Coming Soon
+"use client";
+import { useEventCallback } from "@/cuicui/hooks/use-event-callback";
+import { useEventListener } from "@/cuicui/hooks/use-event-listener";
+import { useCallback, useEffect, useState } from "react";
+
+import type { Dispatch, SetStateAction } from "react";
+
+declare global {
+ // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
+ interface WindowEventMap {
+ "session-storage": CustomEvent;
+ }
+}
+
+type UseSessionStorageOptions = {
+ serializer?: (value: T) => string;
+ deserializer?: (value: string) => T;
+ initializeWithValue?: boolean;
+};
+
+const IS_SERVER = typeof window === "undefined";
+
+export function useSessionStorage(
+ key: string,
+ initialValue: T | (() => T),
+ options: UseSessionStorageOptions = {},
+): [T, Dispatch>, () => void] {
+ const { initializeWithValue = true } = options;
+
+ const serializer = useCallback<(value: T) => string>(
+ (value) => {
+ if (options.serializer) {
+ return options.serializer(value);
+ }
+
+ return JSON.stringify(value);
+ },
+ [options],
+ );
+
+ const deserializer = useCallback<(value: string) => T>(
+ (value) => {
+ if (options.deserializer) {
+ return options.deserializer(value);
+ }
+ // Support 'undefined' as a value
+ if (value === "undefined") {
+ return undefined as unknown as T;
+ }
+
+ const defaultValue =
+ initialValue instanceof Function ? initialValue() : initialValue;
+
+ let parsed: unknown;
+ try {
+ parsed = JSON.parse(value);
+ } catch (error) {
+ console.error("Error parsing JSON:", error);
+ return defaultValue; // Return initialValue if parsing fails
+ }
+
+ return parsed as T;
+ },
+ [options, initialValue],
+ );
+
+ // Get from session storage then
+ // parse stored json or return initialValue
+ const readValue = useCallback((): T => {
+ const initialValueToUse =
+ initialValue instanceof Function ? initialValue() : initialValue;
+
+ // Prevent build error "window is undefined" but keep working
+ if (IS_SERVER) {
+ return initialValueToUse;
+ }
+
+ try {
+ const raw = window.sessionStorage.getItem(key);
+ return raw ? deserializer(raw) : initialValueToUse;
+ } catch (error) {
+ console.warn(`Error reading sessionStorage key “${key}”:`, error);
+ return initialValueToUse;
+ }
+ }, [initialValue, key, deserializer]);
+
+ const [storedValue, setStoredValue] = useState(() => {
+ if (initializeWithValue) {
+ return readValue();
+ }
+
+ return initialValue instanceof Function ? initialValue() : initialValue;
+ });
+
+ // Return a wrapped version of useState's setter function that ...
+ // ... persists the new value to sessionStorage.
+ const setValue: Dispatch> = useEventCallback((value) => {
+ // Prevent build error "window is undefined" but keeps working
+ if (IS_SERVER) {
+ console.warn(
+ `Tried setting sessionStorage key “${key}” even though environment is not a client`,
+ );
+ }
+
+ try {
+ // Allow value to be a function so we have the same API as useState
+ const newValue = value instanceof Function ? value(readValue()) : value;
+
+ // Save to session storage
+ window.sessionStorage.setItem(key, serializer(newValue));
+
+ // Save state
+ setStoredValue(newValue);
+
+ // We dispatch a custom event so every similar useSessionStorage hook is notified
+ window.dispatchEvent(new StorageEvent("session-storage", { key }));
+ } catch (error) {
+ console.warn(`Error setting sessionStorage key “${key}”:`, error);
+ }
+ });
+
+ const removeValue = useEventCallback(() => {
+ // Prevent build error "window is undefined" but keeps working
+ if (IS_SERVER) {
+ console.warn(
+ `Tried removing sessionStorage key “${key}” even though environment is not a client`,
+ );
+ }
+
+ const defaultValue =
+ initialValue instanceof Function ? initialValue() : initialValue;
+
+ // Remove the key from session storage
+ window.sessionStorage.removeItem(key);
+
+ // Save state with default value
+ setStoredValue(defaultValue);
+
+ // We dispatch a custom event so every similar useSessionStorage hook is notified
+ window.dispatchEvent(new StorageEvent("session-storage", { key }));
+ });
+
+ // biome-ignore lint/correctness/useExhaustiveDependencies:
+ useEffect(() => {
+ setStoredValue(readValue());
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [key]);
+
+ const handleStorageChange = useCallback(
+ (event: StorageEvent | CustomEvent) => {
+ if ((event as StorageEvent).key && (event as StorageEvent).key !== key) {
+ return;
+ }
+ setStoredValue(readValue());
+ },
+ [key, readValue],
+ );
+
+ // this only works for other documents, not the current one
+ useEventListener("storage", handleStorageChange);
+
+ // this is a custom event, triggered in writeValueToSessionStorage
+ // See: useSessionStorage()
+ useEventListener("session-storage", handleStorageChange);
+
+ return [storedValue, setValue, removeValue];
+}
+
+export default useSessionStorage;
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 0cb4e69..35353d4 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -20,6 +20,7 @@
"@cuicui/config-typescript": "workspace:*",
"@types/react": "^19.0.1",
"@types/react-dom": "^19.0.2",
+ "@types/web-bluetooth": "^0.0.20",
"@vitejs/plugin-react": "4.3.1",
"shiki": "1.18.0",
"tailwindcss": "^3.4.16",
diff --git a/packages/ui/section-list.ts b/packages/ui/section-list.ts
index b503b13..126a4db 100644
--- a/packages/ui/section-list.ts
+++ b/packages/ui/section-list.ts
@@ -37,15 +37,19 @@ import common_ui_skeletons_category from "@/cuicui/common-ui/skeletons/category"
import common_ui_toggle_category from "@/cuicui/common-ui/toggle/category";
import hooks_use_auto_scroll_category from "@/cuicui/hooks/use-auto-scroll/category";
import hooks_use_battery_category from "@/cuicui/hooks/use-battery/category";
+import hooks_use_bluetooth_category from "@/cuicui/hooks/use-bluetooth/category";
import hooks_use_click_outside_category from "@/cuicui/hooks/use-click-outside/category";
import hooks_use_cookies_category from "@/cuicui/hooks/use-cookies/category";
import hooks_use_copy_to_clipboard_category from "@/cuicui/hooks/use-copy-to-clipboard/category";
import hooks_use_counter_category from "@/cuicui/hooks/use-counter/category";
import hooks_use_debounce_category from "@/cuicui/hooks/use-debounce/category";
+import hooks_use_draggable_category from "@/cuicui/hooks/use-draggable/category";
+import hooks_use_drop_zone_category from "@/cuicui/hooks/use-drop-zone/category";
import hooks_use_event_callback_category from "@/cuicui/hooks/use-event-callback/category";
import hooks_use_event_listener_category from "@/cuicui/hooks/use-event-listener/category";
import hooks_use_first_visit_category from "@/cuicui/hooks/use-first-visit/category";
import hooks_use_geolocation_category from "@/cuicui/hooks/use-geolocation/category";
+import hooks_use_history_category from "@/cuicui/hooks/use-history/category";
import hooks_use_in_view_category from "@/cuicui/hooks/use-in-view/category";
import hooks_use_input_value_category from "@/cuicui/hooks/use-input-value/category";
import hooks_use_isomorphic_layout_effect_category from "@/cuicui/hooks/use-isomorphic-layout-effect/category";
@@ -54,6 +58,7 @@ import hooks_use_konami_code_category from "@/cuicui/hooks/use-konami-code/categ
import hooks_use_local_storage_category from "@/cuicui/hooks/use-local-storage/category";
import hooks_use_location_category from "@/cuicui/hooks/use-location/category";
import hooks_use_measure_category from "@/cuicui/hooks/use-measure/category";
+import hooks_use_memory_category from "@/cuicui/hooks/use-memory/category";
import hooks_use_mouse_category from "@/cuicui/hooks/use-mouse/category";
import hooks_use_network_status_category from "@/cuicui/hooks/use-network-status/category";
import hooks_use_online_status_category from "@/cuicui/hooks/use-online-status/category";
@@ -144,15 +149,19 @@ import common_ui_skeletons_classic_pulse_component from "@/cuicui/common-ui/skel
import common_ui_skeletons_shiny_gradient_component from "@/cuicui/common-ui/skeletons/shiny-gradient/component";
import hooks_use_auto_scroll_hook_component from "@/cuicui/hooks/use-auto-scroll/hook/component";
import hooks_use_battery_hook_component from "@/cuicui/hooks/use-battery/hook/component";
+import hooks_use_bluetooth_hook_component from "@/cuicui/hooks/use-bluetooth/hook/component";
import hooks_use_click_outside_hook_component from "@/cuicui/hooks/use-click-outside/hook/component";
import hooks_use_cookies_hook_component from "@/cuicui/hooks/use-cookies/hook/component";
import hooks_use_copy_to_clipboard_hook_component from "@/cuicui/hooks/use-copy-to-clipboard/hook/component";
import hooks_use_counter_hook_component from "@/cuicui/hooks/use-counter/hook/component";
import hooks_use_debounce_hook_component from "@/cuicui/hooks/use-debounce/hook/component";
+import hooks_use_draggable_hook_component from "@/cuicui/hooks/use-draggable/hook/component";
+import hooks_use_drop_zone_hook_component from "@/cuicui/hooks/use-drop-zone/hook/component";
import hooks_use_event_callback_hook_component from "@/cuicui/hooks/use-event-callback/hook/component";
import hooks_use_event_listener_hook_component from "@/cuicui/hooks/use-event-listener/hook/component";
import hooks_use_first_visit_hook_component from "@/cuicui/hooks/use-first-visit/hook/component";
import hooks_use_geolocation_hook_component from "@/cuicui/hooks/use-geolocation/hook/component";
+import hooks_use_history_hook_component from "@/cuicui/hooks/use-history/hook/component";
import hooks_use_in_view_hook_component from "@/cuicui/hooks/use-in-view/hook/component";
import hooks_use_input_value_hook_component from "@/cuicui/hooks/use-input-value/hook/component";
import hooks_use_isomorphic_layout_effect_hook_component from "@/cuicui/hooks/use-isomorphic-layout-effect/hook/component";
@@ -161,6 +170,7 @@ import hooks_use_konami_code_hook_component from "@/cuicui/hooks/use-konami-code
import hooks_use_local_storage_hook_component from "@/cuicui/hooks/use-local-storage/hook/component";
import hooks_use_location_hook_component from "@/cuicui/hooks/use-location/hook/component";
import hooks_use_measure_hook_component from "@/cuicui/hooks/use-measure/hook/component";
+import hooks_use_memory_hook_component from "@/cuicui/hooks/use-memory/hook/component";
import hooks_use_mouse_hook_component from "@/cuicui/hooks/use-mouse/hook/component";
import hooks_use_network_status_hook_component from "@/cuicui/hooks/use-network-status/hook/component";
import hooks_use_online_status_hook_component from "@/cuicui/hooks/use-online-status/hook/component";
@@ -293,15 +303,19 @@ import common_ui_skeletons_shiny_gradient_horizontal_variant from "@/cuicui/comm
import common_ui_skeletons_shiny_gradient_vertical_variant from "@/cuicui/common-ui/skeletons/shiny-gradient/vertical.variant";
import hooks_use_auto_scroll_hook_use_auto_scroll_variant from "@/cuicui/hooks/use-auto-scroll/hook/use-auto-scroll.variant";
import hooks_use_battery_hook_use_battery_variant from "@/cuicui/hooks/use-battery/hook/use-battery.variant";
+import hooks_use_bluetooth_hook_use_bluetooh_variant from "@/cuicui/hooks/use-bluetooth/hook/use-bluetooh.variant";
import hooks_use_click_outside_hook_use_click_outside_variant from "@/cuicui/hooks/use-click-outside/hook/use-click-outside.variant";
import hooks_use_cookies_hook_use_cookies_variant from "@/cuicui/hooks/use-cookies/hook/use-cookies.variant";
import hooks_use_copy_to_clipboard_hook_copy_to_clipboard_variant from "@/cuicui/hooks/use-copy-to-clipboard/hook/copy-to-clipboard.variant";
import hooks_use_counter_hook_use_counter_variant from "@/cuicui/hooks/use-counter/hook/use-counter.variant";
import hooks_use_debounce_hook_use_debounce_variant from "@/cuicui/hooks/use-debounce/hook/use-debounce.variant";
+import hooks_use_draggable_hook_use_draggable_variant from "@/cuicui/hooks/use-draggable/hook/use-draggable.variant";
+import hooks_use_drop_zone_hook_drop_zone_variant from "@/cuicui/hooks/use-drop-zone/hook/drop-zone.variant";
import hooks_use_event_callback_hook_use_event_callback_variant from "@/cuicui/hooks/use-event-callback/hook/use-event-callback.variant";
import hooks_use_event_listener_hook_use_event_listener_variant from "@/cuicui/hooks/use-event-listener/hook/use-event-listener.variant";
import hooks_use_first_visit_hook_use_first_visit_variant from "@/cuicui/hooks/use-first-visit/hook/use-first-visit.variant";
import hooks_use_geolocation_hook_use_geolocation_variant from "@/cuicui/hooks/use-geolocation/hook/use-geolocation.variant";
+import hooks_use_history_hook_use_history_variant from "@/cuicui/hooks/use-history/hook/use-history.variant";
import hooks_use_in_view_hook_in_view_variant from "@/cuicui/hooks/use-in-view/hook/in-view.variant";
import hooks_use_input_value_hook_use_input_value_variant from "@/cuicui/hooks/use-input-value/hook/use-input-value.variant";
import hooks_use_isomorphic_layout_effect_hook_use_isomorphic_layout_effect_variant from "@/cuicui/hooks/use-isomorphic-layout-effect/hook/use-isomorphic-layout-effect.variant";
@@ -310,6 +324,7 @@ import hooks_use_konami_code_hook_use_konami_code_variant from "@/cuicui/hooks/u
import hooks_use_local_storage_hook_use_local_storage_variant from "@/cuicui/hooks/use-local-storage/hook/use-local-storage.variant";
import hooks_use_location_hook_use_location_variant from "@/cuicui/hooks/use-location/hook/use-location.variant";
import hooks_use_measure_hook_use_measure_variant from "@/cuicui/hooks/use-measure/hook/use-measure.variant";
+import hooks_use_memory_hook_use_memory_variant from "@/cuicui/hooks/use-memory/hook/use-memory.variant";
import hooks_use_mouse_hook_use_mouse_variant from "@/cuicui/hooks/use-mouse/hook/use-mouse.variant";
import hooks_use_network_status_hook_use_network_status_variant from "@/cuicui/hooks/use-network-status/hook/use-network-status.variant";
import hooks_use_online_status_hook_use_online_status_variant from "@/cuicui/hooks/use-online-status/hook/use-online-status.variant";
@@ -1494,6 +1509,25 @@ export const sectionList: SectionType[] = [
},
],
},
+ {
+ meta: hooks_use_bluetooth_category,
+ slug: "use-bluetooth",
+ components: [
+ {
+ meta: hooks_use_bluetooth_hook_component,
+ slug: "hook",
+ variants: [
+ {
+ name: "use-bluetooh",
+ variantComponent: hooks_use_bluetooth_hook_use_bluetooh_variant,
+ slug: "use-bluetooh",
+ pathname:
+ "cuicui/hooks/use-bluetooth/hook/use-bluetooh.variant.tsx",
+ },
+ ],
+ },
+ ],
+ },
{
meta: hooks_use_click_outside_category,
slug: "use-click-outside",
@@ -1591,6 +1625,45 @@ export const sectionList: SectionType[] = [
},
],
},
+ {
+ meta: hooks_use_draggable_category,
+ slug: "use-draggable",
+ components: [
+ {
+ meta: hooks_use_draggable_hook_component,
+ slug: "hook",
+ variants: [
+ {
+ name: "use-draggable",
+ variantComponent:
+ hooks_use_draggable_hook_use_draggable_variant,
+ slug: "use-draggable",
+ pathname:
+ "cuicui/hooks/use-draggable/hook/use-draggable.variant.tsx",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ meta: hooks_use_drop_zone_category,
+ slug: "use-drop-zone",
+ components: [
+ {
+ meta: hooks_use_drop_zone_hook_component,
+ slug: "hook",
+ variants: [
+ {
+ name: "drop-zone",
+ variantComponent: hooks_use_drop_zone_hook_drop_zone_variant,
+ slug: "drop-zone",
+ pathname:
+ "cuicui/hooks/use-drop-zone/hook/drop-zone.variant.tsx",
+ },
+ ],
+ },
+ ],
+ },
{
meta: hooks_use_event_callback_category,
slug: "use-event-callback",
@@ -1671,6 +1744,25 @@ export const sectionList: SectionType[] = [
},
],
},
+ {
+ meta: hooks_use_history_category,
+ slug: "use-history",
+ components: [
+ {
+ meta: hooks_use_history_hook_component,
+ slug: "hook",
+ variants: [
+ {
+ name: "use-history",
+ variantComponent: hooks_use_history_hook_use_history_variant,
+ slug: "use-history",
+ pathname:
+ "cuicui/hooks/use-history/hook/use-history.variant.tsx",
+ },
+ ],
+ },
+ ],
+ },
{
meta: hooks_use_in_view_category,
slug: "use-in-view",
@@ -1827,6 +1919,24 @@ export const sectionList: SectionType[] = [
},
],
},
+ {
+ meta: hooks_use_memory_category,
+ slug: "use-memory",
+ components: [
+ {
+ meta: hooks_use_memory_hook_component,
+ slug: "hook",
+ variants: [
+ {
+ name: "use-memory",
+ variantComponent: hooks_use_memory_hook_use_memory_variant,
+ slug: "use-memory",
+ pathname: "cuicui/hooks/use-memory/hook/use-memory.variant.tsx",
+ },
+ ],
+ },
+ ],
+ },
{
meta: hooks_use_mouse_category,
slug: "use-mouse",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7d22377..c598970 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -79,7 +79,7 @@ importers:
version: 11.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
next:
specifier: 15.1.5
- version: 15.1.5(babel-plugin-react-compiler@19.0.0-beta-e552027-20250112)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ version: 15.1.5(@babel/core@7.26.0)(babel-plugin-react-compiler@19.0.0-beta-e552027-20250112)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
next-mdx-remote:
specifier: 5.0.0
version: 5.0.0(@types/react@19.0.1)(acorn@8.14.0)(react@19.0.0)
@@ -144,6 +144,9 @@ importers:
'@types/react-dom':
specifier: 19.0.2
version: 19.0.2(@types/react@19.0.1)
+ '@types/web-bluetooth':
+ specifier: ^0.0.20
+ version: 0.0.20
'@vitejs/plugin-react':
specifier: 4.3.1
version: 4.3.1(vite@5.4.11(@types/node@20.12.7))
@@ -293,7 +296,7 @@ importers:
version: 11.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
next:
specifier: 15.1.5
- version: 15.1.5(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ version: 15.1.5(@babel/core@7.26.0)(babel-plugin-react-compiler@19.0.0-beta-e552027-20250112)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
next-themes:
specifier: 0.4.4
version: 0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@@ -328,6 +331,9 @@ importers:
'@types/react-dom':
specifier: ^19.0.2
version: 19.0.2(@types/react@19.0.1)
+ '@types/web-bluetooth':
+ specifier: ^0.0.20
+ version: 0.0.20
'@vitejs/plugin-react':
specifier: 4.3.1
version: 4.3.1(vite@5.4.11(@types/node@20.12.7))
@@ -1864,6 +1870,9 @@ packages:
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
+ '@types/web-bluetooth@0.0.20':
+ resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
+
'@types/yauzl@2.10.3':
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
@@ -3307,6 +3316,7 @@ packages:
lodash.isequal@4.5.0:
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
+ deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
lodash.isplainobject@4.0.6:
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
@@ -6303,6 +6313,8 @@ snapshots:
'@types/unist@3.0.3': {}
+ '@types/web-bluetooth@0.0.20': {}
+
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 20.12.7
@@ -8496,32 +8508,7 @@ snapshots:
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
- next@15.1.5(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
- dependencies:
- '@next/env': 15.1.5
- '@swc/counter': 0.1.3
- '@swc/helpers': 0.5.15
- busboy: 1.6.0
- caniuse-lite: 1.0.30001690
- postcss: 8.4.31
- react: 19.0.0
- react-dom: 19.0.0(react@19.0.0)
- styled-jsx: 5.1.6(@babel/core@7.26.0)(react@19.0.0)
- optionalDependencies:
- '@next/swc-darwin-arm64': 15.1.5
- '@next/swc-darwin-x64': 15.1.5
- '@next/swc-linux-arm64-gnu': 15.1.5
- '@next/swc-linux-arm64-musl': 15.1.5
- '@next/swc-linux-x64-gnu': 15.1.5
- '@next/swc-linux-x64-musl': 15.1.5
- '@next/swc-win32-arm64-msvc': 15.1.5
- '@next/swc-win32-x64-msvc': 15.1.5
- sharp: 0.33.5
- transitivePeerDependencies:
- - '@babel/core'
- - babel-plugin-macros
-
- next@15.1.5(babel-plugin-react-compiler@19.0.0-beta-e552027-20250112)(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
+ next@15.1.5(@babel/core@7.26.0)(babel-plugin-react-compiler@19.0.0-beta-e552027-20250112)(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
'@next/env': 15.1.5
'@swc/counter': 0.1.3