@boxcustodia/library 2.0.0-alpha.16 → 2.0.0-alpha.18
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/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +40 -14
- package/dist/index.es.js +310 -282
- package/package.json +1 -1
- package/src/__doc__/V2.mdx +37 -1
- package/src/components/accordion/accordion.test.tsx +117 -0
- package/src/components/alert-dialog/alert-dialog.test.tsx +208 -22
- package/src/components/alert-dialog/alert-dialog.tsx +4 -4
- package/src/components/avatar/avatar.test.tsx +166 -29
- package/src/components/combobox/combobox.tsx +1 -1
- package/src/components/dialog/dialog.tsx +1 -1
- package/src/components/input/input.stories.tsx +2 -3
- package/src/components/input/input.test.tsx +109 -0
- package/src/components/label/label.tsx +1 -1
- package/src/components/number-input/number-input.test.tsx +155 -48
- package/src/components/toast/toast.tsx +1 -1
- package/src/hooks/index.ts +4 -3
- package/src/hooks/use-clipboard/index.ts +1 -0
- package/src/hooks/use-clipboard/use-clipboard.stories.tsx +168 -0
- package/src/hooks/use-clipboard/use-clipboard.test.tsx +83 -0
- package/src/hooks/use-clipboard/use-clipboard.tsx +64 -0
- package/src/hooks/use-document-title/index.ts +1 -0
- package/src/hooks/use-document-title/use-document-title.stories.tsx +72 -0
- package/src/hooks/use-document-title/use-document-title.test.tsx +75 -0
- package/src/hooks/use-document-title/use-document-title.tsx +32 -0
- package/src/hooks/use-hover/index.ts +1 -0
- package/src/hooks/use-hover/use-hover.stories.tsx +90 -0
- package/src/hooks/use-hover/use-hover.test.tsx +93 -0
- package/src/hooks/use-hover/use-hover.tsx +45 -0
- package/src/hooks/use-on-mount/index.ts +1 -0
- package/src/hooks/use-on-mount/use-on-mount.stories.tsx +85 -0
- package/src/hooks/use-on-mount/use-on-mount.test.tsx +44 -0
- package/src/hooks/use-on-mount/use-on-mount.tsx +13 -0
- package/src/utils/form.tsx +0 -2
- package/src/hooks/useClipboard/__doc__/useClipboard.stories.tsx +0 -43
- package/src/hooks/useClipboard/__test__/useClipboard.test.tsx +0 -19
- package/src/hooks/useClipboard/index.ts +0 -1
- package/src/hooks/useClipboard/useClipboard.tsx +0 -28
- package/src/hooks/useDocumentTitle/__doc__/useDocumentTitle.stories.tsx +0 -26
- package/src/hooks/useDocumentTitle/index.ts +0 -1
- package/src/hooks/useDocumentTitle/useDocumentTitle.tsx +0 -11
- package/src/hooks/useHover/__doc__/useHover.stories.tsx +0 -41
- package/src/hooks/useHover/__test__/useHover.test.tsx +0 -45
- package/src/hooks/useHover/index.ts +0 -1
- package/src/hooks/useHover/useHover.tsx +0 -40
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { renderHook } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { useOnMount } from "./use-on-mount";
|
|
4
|
+
|
|
5
|
+
describe("useOnMount", () => {
|
|
6
|
+
it("runs the callback once on mount", () => {
|
|
7
|
+
const fn = vi.fn();
|
|
8
|
+
renderHook(() => useOnMount(fn));
|
|
9
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("does not re-run on subsequent renders", () => {
|
|
13
|
+
const fn = vi.fn();
|
|
14
|
+
const { rerender } = renderHook(() => useOnMount(fn));
|
|
15
|
+
rerender();
|
|
16
|
+
rerender();
|
|
17
|
+
expect(fn).toHaveBeenCalledTimes(1);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("does not re-run when the callback identity changes", () => {
|
|
21
|
+
const fn1 = vi.fn();
|
|
22
|
+
const fn2 = vi.fn();
|
|
23
|
+
const { rerender } = renderHook(({ cb }) => useOnMount(cb), {
|
|
24
|
+
initialProps: { cb: fn1 },
|
|
25
|
+
});
|
|
26
|
+
rerender({ cb: fn2 });
|
|
27
|
+
expect(fn1).toHaveBeenCalledTimes(1);
|
|
28
|
+
expect(fn2).not.toHaveBeenCalled();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("runs the returned cleanup function on unmount", () => {
|
|
32
|
+
const cleanup = vi.fn();
|
|
33
|
+
const { unmount } = renderHook(() => useOnMount(() => cleanup));
|
|
34
|
+
expect(cleanup).not.toHaveBeenCalled();
|
|
35
|
+
|
|
36
|
+
unmount();
|
|
37
|
+
expect(cleanup).toHaveBeenCalledTimes(1);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("does not throw when the callback returns nothing", () => {
|
|
41
|
+
const { unmount } = renderHook(() => useOnMount(() => undefined));
|
|
42
|
+
expect(() => unmount()).not.toThrow();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
export type UseOnMountCallback = () => void | (() => void);
|
|
4
|
+
|
|
5
|
+
export function useOnMount(callback: UseOnMountCallback): void {
|
|
6
|
+
const hasRunRef = useRef(false);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (hasRunRef.current) return;
|
|
10
|
+
hasRunRef.current = true;
|
|
11
|
+
return callback();
|
|
12
|
+
}, []);
|
|
13
|
+
}
|
package/src/utils/form.tsx
CHANGED
|
@@ -44,10 +44,8 @@ export function useHookForm<T extends FieldValues = FieldValues>(
|
|
|
44
44
|
schema: z.ZodType<T>,
|
|
45
45
|
options?: Partial<UseFormProps<T>>,
|
|
46
46
|
): UseFormReturn<T> {
|
|
47
|
-
// @ts-expect-error RHF's UseFormReturn has 3 generics; narrowing to <T> is safe here.
|
|
48
47
|
return rhfUseForm<T>({
|
|
49
48
|
...options,
|
|
50
|
-
// @ts-expect-error zodResolver returns Resolver<any, any, any>; incompatible with Resolver<T, any, T>.
|
|
51
49
|
resolver: zodResolver(schema as any),
|
|
52
50
|
});
|
|
53
51
|
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
-
import { Button } from "../../../components";
|
|
3
|
-
import useClipboard from "../useClipboard";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Permite copiar texto al portapapeles del usuario. Devuelve el texto copiado actualmente y una función para copiar nuevo texto. Incluye manejo de compatibilidad y errores.
|
|
7
|
-
*/
|
|
8
|
-
const meta: Meta = {
|
|
9
|
-
title: "hooks/useClipboard",
|
|
10
|
-
tags: ["autodocs"],
|
|
11
|
-
parameters: {
|
|
12
|
-
docs: {
|
|
13
|
-
source: { type: "code" },
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export default meta;
|
|
19
|
-
type Story = StoryObj<typeof meta>;
|
|
20
|
-
|
|
21
|
-
export const Default: Story = {
|
|
22
|
-
name: "Copia al portapapeles",
|
|
23
|
-
args: {
|
|
24
|
-
textoACopiar: "Hello world",
|
|
25
|
-
},
|
|
26
|
-
render: (args) => {
|
|
27
|
-
const [copiedText, copy] = useClipboard();
|
|
28
|
-
return (
|
|
29
|
-
<div className="space-y-2">
|
|
30
|
-
<pre className="rounded-md bg-slate-950 p-4 text-white">
|
|
31
|
-
<code className="block">
|
|
32
|
-
Texto copiado:{" "}
|
|
33
|
-
<span className="text-blue-300">
|
|
34
|
-
{JSON.stringify(copiedText, null, 2)}
|
|
35
|
-
</span>
|
|
36
|
-
</code>
|
|
37
|
-
</pre>
|
|
38
|
-
|
|
39
|
-
<Button onClick={() => copy(args.textoACopiar)}>Copy</Button>
|
|
40
|
-
</div>
|
|
41
|
-
);
|
|
42
|
-
},
|
|
43
|
-
};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { renderHook, waitFor } from "@testing-library/react";
|
|
2
|
-
import { describe, expect, it } from "vitest";
|
|
3
|
-
import useClipboard from "../useClipboard";
|
|
4
|
-
|
|
5
|
-
describe("useClipboard", () => {
|
|
6
|
-
it("should be defined", () => {
|
|
7
|
-
expect(useClipboard).toBeDefined();
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it("should copy to clipboard", () => {
|
|
11
|
-
const { result } = renderHook(useClipboard);
|
|
12
|
-
const [copiedValue, copy] = result.current;
|
|
13
|
-
|
|
14
|
-
copy("test");
|
|
15
|
-
waitFor(() => {
|
|
16
|
-
expect(copiedValue).toEqual("test");
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as useClipboard } from "./useClipboard";
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { useCallback, useState } from "react";
|
|
2
|
-
|
|
3
|
-
type CopiedValue = string | null;
|
|
4
|
-
type CopyFunction = (text: string) => Promise<boolean>;
|
|
5
|
-
|
|
6
|
-
function useCopyToClipboard(): [CopiedValue, CopyFunction] {
|
|
7
|
-
const [copiedText, setCopiedText] = useState<CopiedValue>(null);
|
|
8
|
-
|
|
9
|
-
const copy = useCallback(async (text: string) => {
|
|
10
|
-
if (!navigator?.clipboard) {
|
|
11
|
-
return false;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
await navigator.clipboard.writeText(text);
|
|
16
|
-
setCopiedText(text);
|
|
17
|
-
return true;
|
|
18
|
-
} catch (error) {
|
|
19
|
-
console.error(error);
|
|
20
|
-
setCopiedText(null);
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
}, []);
|
|
24
|
-
|
|
25
|
-
return [copiedText, copy];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export default useCopyToClipboard;
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
-
import { useState } from "react";
|
|
3
|
-
import { Button, Input } from "../../../components";
|
|
4
|
-
import useDocumentTitle from "../useDocumentTitle";
|
|
5
|
-
|
|
6
|
-
const meta: Meta = {
|
|
7
|
-
title: "hooks/useDocumentTitle",
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export default meta;
|
|
11
|
-
|
|
12
|
-
type Story = StoryObj<typeof meta>;
|
|
13
|
-
|
|
14
|
-
export const Default: Story = {
|
|
15
|
-
render: () => {
|
|
16
|
-
const [title, setTitle] = useState("");
|
|
17
|
-
useDocumentTitle(title);
|
|
18
|
-
|
|
19
|
-
return (
|
|
20
|
-
<div className="space-y-2">
|
|
21
|
-
<Input onValueChange={setTitle} value={title} />
|
|
22
|
-
<Button onClick={() => setTitle(title)}>Set document title</Button>
|
|
23
|
-
</div>
|
|
24
|
-
);
|
|
25
|
-
},
|
|
26
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as useDocumentTitle } from "./useDocumentTitle";
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { Meta } from "@storybook/react-vite";
|
|
2
|
-
import { cn } from "../../../lib";
|
|
3
|
-
import useHover from "../useHover";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Hook que facilita el manejo de un estado booleano. Provee métodos para abrir, cerrar y toggle el estado
|
|
7
|
-
*/
|
|
8
|
-
const meta: Meta = {
|
|
9
|
-
title: "hooks/useHover",
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export default meta;
|
|
13
|
-
|
|
14
|
-
export const Default = {
|
|
15
|
-
render: () => {
|
|
16
|
-
const { ref, isHovering } = useHover<HTMLDivElement>();
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<div className="space-y-2">
|
|
20
|
-
<div
|
|
21
|
-
ref={ref}
|
|
22
|
-
className={cn(
|
|
23
|
-
"rounded transition-colors w-32 h-32 shadow text-3xl grid place-items-center cursor-pointer",
|
|
24
|
-
{ "bg-accent": isHovering },
|
|
25
|
-
)}
|
|
26
|
-
>
|
|
27
|
-
🍅
|
|
28
|
-
</div>
|
|
29
|
-
|
|
30
|
-
<pre className="rounded-md bg-slate-950 p-4 text-white">
|
|
31
|
-
<code className="block">
|
|
32
|
-
hover?:{" "}
|
|
33
|
-
<span className={isHovering ? "text-green-500" : "text-red-500"}>
|
|
34
|
-
{JSON.stringify(isHovering, null, 2)}
|
|
35
|
-
</span>
|
|
36
|
-
</code>
|
|
37
|
-
</pre>
|
|
38
|
-
</div>
|
|
39
|
-
);
|
|
40
|
-
},
|
|
41
|
-
};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { fireEvent } from "@testing-library/dom";
|
|
2
|
-
import { render, screen, waitFor } from "@testing-library/react";
|
|
3
|
-
import { describe, expect, it, vi } from "vitest";
|
|
4
|
-
import useHover from "../useHover";
|
|
5
|
-
|
|
6
|
-
describe("useHover hook", () => {
|
|
7
|
-
it("should be defined", () => {
|
|
8
|
-
expect(useHover).toBeDefined();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it("should handle hover start and end events", async () => {
|
|
12
|
-
const handleHoverStart = vi.fn();
|
|
13
|
-
const handleHoverEnd = vi.fn();
|
|
14
|
-
|
|
15
|
-
const Component = () => {
|
|
16
|
-
const { ref, isHovering } = useHover<HTMLDivElement>({
|
|
17
|
-
onHoverStart: handleHoverStart,
|
|
18
|
-
onHoverEnd: handleHoverEnd,
|
|
19
|
-
});
|
|
20
|
-
return (
|
|
21
|
-
<div ref={ref} data-testid="hover-target">
|
|
22
|
-
{isHovering ? "Hovering" : "Not Hovering"}
|
|
23
|
-
</div>
|
|
24
|
-
);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
render(<Component />);
|
|
28
|
-
|
|
29
|
-
const target = screen.getByTestId("hover-target");
|
|
30
|
-
|
|
31
|
-
fireEvent.mouseOver(target);
|
|
32
|
-
|
|
33
|
-
await waitFor(() => {
|
|
34
|
-
expect(handleHoverStart).toHaveBeenCalled();
|
|
35
|
-
expect(target.textContent).toBe("Hovering");
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
fireEvent.mouseOut(target);
|
|
39
|
-
|
|
40
|
-
await waitFor(() => {
|
|
41
|
-
expect(handleHoverEnd).toHaveBeenCalled();
|
|
42
|
-
expect(target.textContent).toBe("Not Hovering");
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default as useHover } from "./useHover";
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from "react";
|
|
2
|
-
|
|
3
|
-
type UseHoverOptions = {
|
|
4
|
-
onHoverStart?: (event: MouseEvent) => void;
|
|
5
|
-
onHoverEnd?: (event: MouseEvent) => void;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
function useHover<T extends HTMLElement>(
|
|
9
|
-
options?: UseHoverOptions,
|
|
10
|
-
): { ref: React.RefObject<T | null>; isHovering: boolean } {
|
|
11
|
-
const [isHovering, setIsHovering] = useState(false);
|
|
12
|
-
const ref = useRef<T | null>(null);
|
|
13
|
-
|
|
14
|
-
useEffect(() => {
|
|
15
|
-
const handleMouseOver = (event: MouseEvent) => {
|
|
16
|
-
setIsHovering(true);
|
|
17
|
-
options?.onHoverStart?.(event);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const handleMouseOut = (event: MouseEvent) => {
|
|
21
|
-
setIsHovering(false);
|
|
22
|
-
options?.onHoverEnd?.(event);
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const node = ref.current;
|
|
26
|
-
if (node) {
|
|
27
|
-
node.addEventListener("mouseover", handleMouseOver);
|
|
28
|
-
node.addEventListener("mouseout", handleMouseOut);
|
|
29
|
-
|
|
30
|
-
return () => {
|
|
31
|
-
node.removeEventListener("mouseover", handleMouseOver);
|
|
32
|
-
node.removeEventListener("mouseout", handleMouseOut);
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
}, [options]);
|
|
36
|
-
|
|
37
|
-
return { ref, isHovering };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export default useHover;
|