@usefui/components 1.5.1 → 1.5.3

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.
Files changed (42) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.d.mts +237 -9
  3. package/dist/index.d.ts +237 -9
  4. package/dist/index.js +1095 -311
  5. package/dist/index.mjs +1049 -278
  6. package/package.json +6 -6
  7. package/src/avatar/index.tsx +1 -1
  8. package/src/badge/index.tsx +3 -3
  9. package/src/breadcrumb/Breadcrumb.stories.tsx +36 -0
  10. package/src/breadcrumb/index.tsx +117 -0
  11. package/src/breadcrumb/styles/index.ts +19 -0
  12. package/src/button/Button.stories.tsx +80 -8
  13. package/src/button/index.tsx +67 -5
  14. package/src/button/styles/index.ts +82 -10
  15. package/src/card/Card.stories.tsx +57 -0
  16. package/src/card/index.tsx +55 -0
  17. package/src/card/styles/index.ts +72 -0
  18. package/src/copy-button/CopyButton.stories.tsx +29 -0
  19. package/src/copy-button/index.tsx +101 -0
  20. package/src/dialog/index.tsx +1 -1
  21. package/src/dropdown/index.tsx +1 -1
  22. package/src/field/Field.stories.tsx +39 -8
  23. package/src/field/index.tsx +5 -0
  24. package/src/field/styles/index.ts +38 -12
  25. package/src/index.ts +8 -0
  26. package/src/page/index.tsx +1 -1
  27. package/src/privacy-field/PrivacyField.stories.tsx +29 -0
  28. package/src/privacy-field/index.tsx +56 -0
  29. package/src/privacy-field/styles/index.ts +17 -0
  30. package/src/resizable/Resizable.stories.tsx +40 -0
  31. package/src/resizable/index.tsx +108 -0
  32. package/src/resizable/styles/index.ts +65 -0
  33. package/src/skeleton/Skeleton.stories.tsx +32 -0
  34. package/src/skeleton/index.tsx +43 -0
  35. package/src/skeleton/styles/index.ts +56 -0
  36. package/src/spinner/Spinner.stories.tsx +27 -0
  37. package/src/spinner/index.tsx +19 -0
  38. package/src/spinner/styles/index.ts +43 -0
  39. package/src/text-area/Textarea.stories.tsx +32 -0
  40. package/src/text-area/index.tsx +78 -0
  41. package/src/text-area/styles/index.ts +84 -0
  42. package/src/tooltip/index.tsx +4 -3
@@ -0,0 +1,40 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { Page, Resizable } from "..";
4
+
5
+ /**
6
+ * Resizable are used to render children into separated sections that can be resized by users.
7
+ */
8
+ const meta = {
9
+ title: "Components/Resizable",
10
+ component: Resizable,
11
+ tags: ["autodocs"],
12
+ } satisfies Meta<typeof Resizable>;
13
+ export default meta;
14
+
15
+ type Story = StoryObj<typeof meta>;
16
+ export const Default: Story = {
17
+ args: {
18
+ defaultWidth: 30,
19
+ left: (
20
+ <div
21
+ className="h-100 w-100 flex align-center justify-center"
22
+ style={{ borderRight: "1px solid var(--font-color-alpha-10)" }}
23
+ >
24
+ <p className="fs-medium-20">One</p>
25
+ </div>
26
+ ),
27
+ right: (
28
+ <div className="h-100 w-100 flex align-center justify-center">
29
+ <p className="fs-medium-20">Two</p>
30
+ </div>
31
+ ),
32
+ },
33
+ render: ({ ...args }) => (
34
+ <Page>
35
+ <Page.Content>
36
+ <Resizable {...args} />
37
+ </Page.Content>
38
+ </Page>
39
+ ),
40
+ };
@@ -0,0 +1,108 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+
5
+ import {
6
+ SplitContainer,
7
+ Panel,
8
+ Divider,
9
+ DragHandle,
10
+ DragIndicator,
11
+ DragOverlay,
12
+ } from "./styles";
13
+
14
+ type ResizableEditorProperties = {
15
+ defaultWidth?: number;
16
+ left: React.ReactNode;
17
+ right: React.ReactNode;
18
+ };
19
+
20
+ /**
21
+ * Resizable are used to render children into separated sections that can be resized by users.
22
+ *
23
+ * @param {ResizableEditorProperties} props - The props for the Resizable component.
24
+ * @param {number} props.defaultWidth - The default width of the Resizable left section.
25
+ * @param {ReactNode} props.left - The content to be rendered inside the Left panel of the Resizable component.
26
+ * @param {ReactNode} props.right - The content to be rendered inside the Right panel of the Resizable component.
27
+ * @returns {ReactElement} The Resizable component.
28
+ */
29
+ export const Resizable = ({
30
+ defaultWidth,
31
+ left,
32
+ right,
33
+ }: ResizableEditorProperties) => {
34
+ const containerRef = React.useRef<HTMLDivElement>(null);
35
+
36
+ const [leftWidth, setLeftWidth] = React.useState(defaultWidth ?? 50); // Percentage
37
+ const [isDragging, setIsDragging] = React.useState(false);
38
+
39
+ const handleMouseDown = React.useCallback(() => setIsDragging(true), []);
40
+ const handleMouseUp = React.useCallback(() => setIsDragging(false), []);
41
+
42
+ const handleMouseMove = React.useCallback(
43
+ (e: MouseEvent) => {
44
+ if (!isDragging || !containerRef.current) return;
45
+
46
+ const containerRect = containerRef.current.getBoundingClientRect();
47
+ const newLeftWidth =
48
+ ((e.clientX - containerRect.left) / containerRect.width) * 100;
49
+
50
+ // Constrain between 20% and 80%
51
+ const threshold = { min: 30, max: 70 };
52
+
53
+ const constrainedWidth = Math.min(
54
+ Math.max(newLeftWidth, threshold.min),
55
+ threshold.max
56
+ );
57
+ setLeftWidth(constrainedWidth);
58
+ },
59
+ [isDragging]
60
+ );
61
+
62
+ React.useEffect(() => {
63
+ if (isDragging) {
64
+ document.addEventListener("mousemove", handleMouseMove);
65
+ document.addEventListener("mouseup", handleMouseUp);
66
+ document.body.style.cursor = "col-resize";
67
+ document.body.style.userSelect = "none";
68
+ } else {
69
+ document.removeEventListener("mousemove", handleMouseMove);
70
+ document.removeEventListener("mouseup", handleMouseUp);
71
+ document.body.style.cursor = "";
72
+ document.body.style.userSelect = "";
73
+ }
74
+
75
+ return () => {
76
+ document.removeEventListener("mousemove", handleMouseMove);
77
+ document.removeEventListener("mouseup", handleMouseUp);
78
+ document.body.style.cursor = "";
79
+ document.body.style.userSelect = "";
80
+ };
81
+ }, [isDragging, handleMouseMove, handleMouseUp]);
82
+
83
+ return (
84
+ <React.Fragment>
85
+ <SplitContainer ref={containerRef} className="h-100 flex">
86
+ <Panel width={leftWidth}>{left}</Panel>
87
+
88
+ <Divider
89
+ $dragging={isDragging}
90
+ onMouseDown={handleMouseDown}
91
+ onTouchStart={handleMouseDown}
92
+ >
93
+ <DragHandle
94
+ className="flex align-center justify-center"
95
+ id="drag-handle"
96
+ >
97
+ <DragIndicator className="drag-indicator-handle" />
98
+ </DragHandle>
99
+ </Divider>
100
+
101
+ <Panel width={100 - leftWidth}>{right}</Panel>
102
+ </SplitContainer>
103
+
104
+ {isDragging && <DragOverlay />}
105
+ </React.Fragment>
106
+ );
107
+ };
108
+ Resizable.displayName = "Resizable";
@@ -0,0 +1,65 @@
1
+ "use client";
2
+
3
+ import styled from "styled-components";
4
+
5
+ export const SplitContainer = styled.div`
6
+ position: relative;
7
+ `;
8
+ export const Panel = styled.div<{ width: number }>`
9
+ overflow: hidden;
10
+ width: ${(props) => props.width}%;
11
+ `;
12
+ export const Divider = styled.div<{ $dragging: boolean }>`
13
+ width: var(--measurement-medium-10);
14
+ height: 100%;
15
+ top: 0;
16
+
17
+ border-radius: var(--measurement-medium-60);
18
+ background-color: transparent;
19
+
20
+ /* background-color: ${(props) =>
21
+ props.$dragging ? "var(--font-color-alpha-10)" : "transparent"}; */
22
+
23
+ cursor: col-resize;
24
+ transition: background-color 0.2s;
25
+ position: relative;
26
+
27
+ /** Shows DragIndicator on hover */
28
+ &:hover .drag-indicator-handle,
29
+ &:active .drag-indicator-handle {
30
+ opacity: 1;
31
+ }
32
+
33
+ .drag-indicator-handle {
34
+ height: ${(props) =>
35
+ props.$dragging
36
+ ? "var(--measurement-large-10)"
37
+ : "var(--measurement-medium-60)"};
38
+ }
39
+ `;
40
+ export const DragHandle = styled.div`
41
+ position: absolute;
42
+ top: 0;
43
+ bottom: 0;
44
+ left: calc(var(--measurement-medium-10) * -1);
45
+ right: calc(var(--measurement-medium-10) * -1);
46
+ `;
47
+ export const DragIndicator = styled.div`
48
+ position: fixed;
49
+ width: var(--measurement-medium-10);
50
+ /* height: var(--measurement-medium-60); */
51
+ background-color: var(--font-color-alpha-60);
52
+ border-radius: var(--measurement-large-10);
53
+
54
+ opacity: 0;
55
+ transition: all 0.2s;
56
+ `;
57
+ export const DragOverlay = styled.div`
58
+ position: fixed;
59
+ top: 0;
60
+ left: 0;
61
+ right: 0;
62
+ bottom: 0;
63
+ z-index: var(--depth-default-90);
64
+ cursor: col-resize;
65
+ `;
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { Page, Skeleton } from "..";
4
+
5
+ /** */
6
+ const meta = {
7
+ title: "Components/Skeleton",
8
+ component: Skeleton,
9
+ tags: ["autodocs"],
10
+ } satisfies Meta<typeof Skeleton>;
11
+ export default meta;
12
+
13
+ type Story = StoryObj<typeof meta>;
14
+ export const Default: Story = {
15
+ args: {},
16
+ render: ({ ...args }) => (
17
+ <Page>
18
+ <Page.Content className="flex flex-column g-medium-30">
19
+ <div className="flex align-center justify-center ">
20
+ <Skeleton sizing="small" />
21
+ <Skeleton sizing="medium" />
22
+ <Skeleton sizing="large" />
23
+ </div>
24
+ <div className="flex align-center justify-center">
25
+ <Skeleton shape="square" />
26
+ <Skeleton shape="smooth" />
27
+ <Skeleton shape="round" />
28
+ </div>
29
+ </Page.Content>
30
+ </Page>
31
+ ),
32
+ };
@@ -0,0 +1,43 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { SkeletonLoader } from "./styles";
5
+
6
+ import {
7
+ ComponentSizeEnum,
8
+ type IComponentSize,
9
+ type TComponentShape,
10
+ } from "../../../../types";
11
+
12
+ export interface SkeletonProperties
13
+ extends IComponentSize,
14
+ React.ComponentPropsWithRef<"span"> {
15
+ shape?: TComponentShape;
16
+ }
17
+
18
+ /**
19
+ * Skeletons are used to convoy a loading state information.
20
+ *
21
+ * @param {IButtonProperties} props - The props for the Skeleton component.
22
+ * @param {ComponentSizeEnum} props.sizing - The size of the component. Defaults to `medium`.
23
+ * @param {TComponentShape} props.shape - The size of the component. Defaults to `smooth`.
24
+ * @returns {ReactElement} The Skeleton component.
25
+ */
26
+ export const Skeleton = (props: SkeletonProperties): React.ReactElement => {
27
+ const {
28
+ sizing = ComponentSizeEnum.Medium,
29
+ shape = "smooth",
30
+ ...restProps
31
+ } = props;
32
+
33
+ return (
34
+ <SkeletonLoader
35
+ data-size={sizing}
36
+ data-shape={shape}
37
+ tabIndex={0}
38
+ {...restProps}
39
+ />
40
+ );
41
+ };
42
+
43
+ Skeleton.displayName = "Skeleton";
@@ -0,0 +1,56 @@
1
+ "use client";
2
+
3
+ import styled, { css, keyframes } from "styled-components";
4
+
5
+ const SkeletonBlink = keyframes`
6
+ 0% {
7
+ opacity: 0.3;
8
+ }
9
+ 100% {
10
+ opacity: 0.1;
11
+ }
12
+ `;
13
+
14
+ const SkeletonBaseStyles = css`
15
+ background: linear-gradient(
16
+ 45deg,
17
+ var(--font-color-alpha-10),
18
+ var(--font-color-alpha-20)
19
+ );
20
+ animation: ${SkeletonBlink} 1s ease-in-out alternate-reverse infinite;
21
+ `;
22
+ const SkeletonSizeStyles = css`
23
+ &[data-size="small"] {
24
+ width: 100%;
25
+
26
+ min-width: var(--measurement-medium-60);
27
+ min-height: var(--measurement-medium-70);
28
+ }
29
+ &[data-size="medium"] {
30
+ width: 100%;
31
+ min-width: var(--measurement-medium-90);
32
+ min-height: var(--measurement-medium-80);
33
+ }
34
+ &[data-size="large"] {
35
+ width: 100%;
36
+ min-width: var(--measurement-medium-90);
37
+ min-height: var(--measurement-medium-90);
38
+ }
39
+ `;
40
+ const SkeletonShapeStyles = css`
41
+ &[data-shape="square"] {
42
+ border-radius: 0;
43
+ }
44
+ &[data-shape="smooth"] {
45
+ border-radius: var(--measurement-medium-20);
46
+ }
47
+ &[data-shape="round"] {
48
+ border-radius: var(--measurement-large-90);
49
+ }
50
+ `;
51
+
52
+ export const SkeletonLoader = styled.span`
53
+ ${SkeletonBaseStyles}
54
+ ${SkeletonSizeStyles}
55
+ ${SkeletonShapeStyles}
56
+ `;
@@ -0,0 +1,27 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+ import { Page, Spinner } from "..";
4
+
5
+ /**
6
+ * Spinners are used to convey a pending state.
7
+ */
8
+ const meta = {
9
+ title: "Components/Spinner",
10
+ component: Spinner,
11
+ tags: ["autodocs"],
12
+ } satisfies Meta<typeof Spinner>;
13
+ export default meta;
14
+
15
+ type Story = StoryObj<typeof meta>;
16
+ export const Default: Story = {
17
+ args: {},
18
+ render: ({ ...args }) => (
19
+ <Page>
20
+ <Page.Content className="g-medium-60 flex align-center justify-center h-100 w-100">
21
+ <Spinner sizing="small" {...args} />
22
+ <Spinner sizing="medium" {...args} />
23
+ <Spinner sizing="large" {...args} />
24
+ </Page.Content>
25
+ </Page>
26
+ ),
27
+ };
@@ -0,0 +1,19 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+
5
+ import { RotatingSpinner } from "./styles";
6
+ import type { IComponentSize } from "../../../../types";
7
+
8
+ interface SpinnerProperties extends IComponentSize {}
9
+
10
+ /**
11
+ * Spinners are used to convoy a loading state information.
12
+ *
13
+ * @param {TextareaProps} props - The props for the Spinner component.
14
+ * @param {ComponentSizeEnum} props.sizing - The size of the component. Defaults to `medium`.
15
+ * @returns {ReactElement} The Spinner component.
16
+ */
17
+ export const Spinner = (props: SpinnerProperties) => {
18
+ return <RotatingSpinner data-size={props.sizing ?? "medium"} />;
19
+ };
@@ -0,0 +1,43 @@
1
+ "use client";
2
+
3
+ import styled, { css, keyframes } from "styled-components";
4
+
5
+ const Rotate = keyframes`
6
+ 0% {
7
+ transform: rotate(0deg);
8
+ }
9
+ 100% {
10
+ transform: rotate(360deg);
11
+ }
12
+ `;
13
+
14
+ const FieldSizeStyles = css`
15
+ &[data-size="small"] {
16
+ width: var(--measurement-medium-40);
17
+ height: var(--measurement-medium-40);
18
+ }
19
+ &[data-size="medium"] {
20
+ width: var(--measurement-medium-50);
21
+ height: var(--measurement-medium-50);
22
+ }
23
+ &[data-size="large"] {
24
+ width: var(--measurement-medium-60);
25
+ height: var(--measurement-medium-60);
26
+ }
27
+ `;
28
+
29
+ export const RotatingSpinner = styled.span`
30
+ padding: 0;
31
+ margin: 0;
32
+
33
+ display: inline-block;
34
+ box-sizing: border-box;
35
+
36
+ border: var(--measurement-small-60) solid var(--font-color-alpha-30);
37
+ border-bottom-color: transparent;
38
+ border-radius: var(--measurement-large-90);
39
+
40
+ animation: ${Rotate} 1s linear infinite;
41
+
42
+ ${FieldSizeStyles}
43
+ `;
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react";
3
+
4
+ import { Page, Textarea } from "..";
5
+ import { TComponentSize } from "../../../../types";
6
+
7
+ /**
8
+ * Textarea are used to allow users to write large chunks of text.
9
+ */
10
+ const meta = {
11
+ title: "Components/Textarea",
12
+ component: Textarea,
13
+ tags: ["autodocs"],
14
+ } satisfies Meta<typeof Textarea>;
15
+ export default meta;
16
+
17
+ type Story = StoryObj<typeof meta>;
18
+ export const Default: Story = {
19
+ args: {
20
+ variant: "secondary",
21
+ resizable: false,
22
+ },
23
+ render: ({ ...args }) => (
24
+ <Page>
25
+ <Page.Content className="p-large-30 flex flex-column g-large-10">
26
+ {["small", "medium", "large"].map((size) => (
27
+ <Textarea key={size} sizing={size as TComponentSize} {...args} />
28
+ ))}
29
+ </Page.Content>
30
+ </Page>
31
+ ),
32
+ };
@@ -0,0 +1,78 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+
5
+ import { TextAreaContainer } from "./styles";
6
+ import {
7
+ IComponentSize,
8
+ IComponentStyling,
9
+ IComponentVariant,
10
+ TComponentShape,
11
+ ComponentSizeEnum,
12
+ } from "../../../../types";
13
+
14
+ export type ScrollContainerProps = {
15
+ $height?: string;
16
+ $width?: string;
17
+ $thumbColor?: string;
18
+ $trackColor?: string;
19
+ $thumbHoverColor?: string;
20
+ };
21
+
22
+ export interface TextareaProps
23
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement>,
24
+ IComponentStyling,
25
+ IComponentSize,
26
+ IComponentVariant {
27
+ resizable?: boolean;
28
+ shape?: "square" | "smooth";
29
+ }
30
+
31
+ /**
32
+ * Textarea are used to allow users to write large chunks of text.
33
+ *
34
+ * @param {TextareaProps} props - The props for the Textarea component.
35
+ * @param {string} props.variant - The style definition used by the component. Defaults to `secondary`.
36
+ * @param {TComponentShape} props.shape - The size of the component. Defaults to `smooth`.
37
+ * @param {ComponentSizeEnum} props.sizing - The size of the component. Defaults to `medium`.
38
+ * @param {boolean} props.resizable - Define whether the component is resizable or not. Defaults to 'true'.
39
+ *
40
+ * @returns {ReactElement} The TextareaProps component.
41
+ */
42
+ export const Textarea = (props: TextareaProps) => {
43
+ const { raw, shape, sizing, variant, resizable, onChange } = props;
44
+
45
+ const textareaRef = React.useRef<HTMLTextAreaElement>(null);
46
+
47
+ const adjustHeight = () => {
48
+ const textarea = textareaRef.current;
49
+ if (textarea) {
50
+ textarea.style.height = "auto";
51
+ textarea.style.height = `${Math.min(
52
+ textarea.scrollHeight,
53
+ parseInt(getComputedStyle(textarea).maxHeight)
54
+ )}px`;
55
+ }
56
+ };
57
+
58
+ const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
59
+ adjustHeight();
60
+ onChange?.(e);
61
+ };
62
+
63
+ React.useEffect(() => adjustHeight(), [props.value]);
64
+
65
+ return (
66
+ <TextAreaContainer
67
+ ref={textareaRef}
68
+ onChange={handleChange}
69
+ data-variant={variant ?? "secondary"}
70
+ data-shape={shape ?? "smooth"}
71
+ data-size={sizing ?? ComponentSizeEnum.Medium}
72
+ data-resizable={resizable}
73
+ data-raw={String(Boolean(raw))}
74
+ {...props}
75
+ />
76
+ );
77
+ };
78
+ Textarea.displayName = "Textarea";
@@ -0,0 +1,84 @@
1
+ "use client";
2
+
3
+ import styled, { css } from "styled-components";
4
+ import {
5
+ FieldShapeStyles,
6
+ FieldDefaultStyles,
7
+ FieldVariantsStyles,
8
+ } from "../../field/styles";
9
+
10
+ import type { ScrollContainerProps } from "text-area";
11
+
12
+ const CustomScrollbar = css<ScrollContainerProps>`
13
+ height: ${({ $height }) => $height ?? "100%"};
14
+ width: ${({ $width }) => $width ?? "100%"};
15
+ overflow-y: auto;
16
+ overflow-x: hidden;
17
+
18
+ &::-webkit-scrollbar {
19
+ cursor: pointer;
20
+
21
+ width: var(--measurement-medium-10);
22
+ }
23
+
24
+ &::-webkit-scrollbar-track {
25
+ background: ${({ $trackColor }) => $trackColor ?? "transparent"};
26
+ border-radius: var(--measurement-medium-30);
27
+ border: none;
28
+ }
29
+
30
+ &::-webkit-scrollbar-thumb {
31
+ background: ${({ $thumbColor }) =>
32
+ $thumbColor ?? "var(--font-color-alpha-10)"};
33
+ border-radius: var(--measurement-medium-30);
34
+ transition: background-color 0.2s ease;
35
+ }
36
+
37
+ &::-webkit-scrollbar-thumb:hover {
38
+ background: ${({ $thumbHoverColor, $thumbColor }) =>
39
+ $thumbHoverColor ?? $thumbColor ?? "var(--font-color-alpha-20)"};
40
+ }
41
+
42
+ // Firefox
43
+ scrollbar-width: thin;
44
+ scrollbar-color: ${({ $thumbColor, $trackColor }) =>
45
+ `${$thumbColor ?? "var(--font-color-alpha-10)"} ${
46
+ $trackColor ?? "transparent"
47
+ }`};
48
+ `;
49
+ const TextareaSizeStyles = css`
50
+ padding: var(--measurement-medium-30);
51
+ max-height: var(--measurement-large-60);
52
+
53
+ &[data-size="small"] {
54
+ min-height: var(--measurement-large-30);
55
+ }
56
+ &[data-size="medium"] {
57
+ min-height: var(--measurement-large-50);
58
+ }
59
+ &[data-size="large"] {
60
+ min-height: var(--measurement-large-60);
61
+ max-height: var(--measurement-large-80);
62
+ }
63
+ `;
64
+ const TextareaResizableStyles = css`
65
+ &[data-resizable="true"] {
66
+ resize: vertical;
67
+ }
68
+ &[data-resizable="false"] {
69
+ resize: none;
70
+ }
71
+ `;
72
+
73
+ export const TextAreaContainer = styled.textarea<ScrollContainerProps>`
74
+ &[data-raw="false"] {
75
+ ${FieldDefaultStyles}
76
+ ${FieldShapeStyles}
77
+
78
+ ${TextareaSizeStyles}
79
+ ${TextareaResizableStyles}
80
+
81
+ ${CustomScrollbar}
82
+ ${FieldVariantsStyles}
83
+ }
84
+ `;
@@ -59,11 +59,12 @@ const Tooltip = ({
59
59
  const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
60
60
 
61
61
  const contentRect = () => contentRef?.current?.getBoundingClientRect();
62
- const bodyRect = React.useCallback(() => {
62
+ const bodyRect = (): DOMRect | undefined => {
63
63
  if (typeof document !== "undefined") {
64
- return document.body.getBoundingClientRect();
64
+ return document?.body?.getBoundingClientRect();
65
65
  }
66
- }, []);
66
+ return undefined;
67
+ };
67
68
 
68
69
  const positions = {
69
70
  btt: `calc((${triggerProps?.top}px - ${contentProps?.height}px) - (var(--measurement-medium-10)))`,