oneslash-design-system 1.0.4 → 1.0.8
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/components/{alert/alert.tsx → alert.tsx} +1 -1
- package/components/button.tsx +121 -0
- package/components/{checkBox/checkBox.tsx → checkBox.tsx} +2 -2
- package/components/emptyBox.tsx +33 -0
- package/components/loadingScreen.tsx +18 -0
- package/components/modal.tsx +66 -0
- package/components/popover.tsx +98 -0
- package/components/{tab/tab.tsx → tab.tsx} +40 -24
- package/components/{tag/tag.tsx → tag.tsx} +1 -1
- package/components/{textField/textField.tsx → textField.tsx} +45 -37
- package/components/timeStamp.tsx +65 -0
- package/components/{tooltip/tooltip.tsx → tooltip.tsx} +1 -5
- package/index.css +5 -1
- package/index.ts +17 -13
- package/package.json +1 -1
- package/components/button/button.tsx +0 -118
- package/components/modal/modal.tsx +0 -49
- package/components/popover/popover.tsx +0 -88
- package/global.d.ts +0 -155
- /package/components/{iconButton/iconButton.tsx → iconButton.tsx} +0 -0
- /package/components/{menuItem/menuItem.tsx → menuItem.tsx} +0 -0
- /package/components/{userImage/userImage.tsx → userImage.tsx} +0 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
interface ButtonProps{
|
|
4
|
+
size: 'small' | 'medium' | 'large';
|
|
5
|
+
type: 'primary' | 'secondary' | 'tertiary' | 'textOnly';
|
|
6
|
+
state: 'enabled' | 'hovered' | 'focused' | 'disabled';
|
|
7
|
+
label: string;
|
|
8
|
+
decoIcon?: string;
|
|
9
|
+
actionIcon?: string;
|
|
10
|
+
onClickButton?: any;
|
|
11
|
+
onClickActionIcon?: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type IconType = React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
|
15
|
+
|
|
16
|
+
export default function Button({
|
|
17
|
+
size,
|
|
18
|
+
type,
|
|
19
|
+
state,
|
|
20
|
+
label,
|
|
21
|
+
decoIcon,
|
|
22
|
+
actionIcon,
|
|
23
|
+
onClickButton,
|
|
24
|
+
onClickActionIcon,
|
|
25
|
+
}: ButtonProps) {
|
|
26
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
27
|
+
const [IconLeft, setIconLeft] = useState<IconType | null>(null);
|
|
28
|
+
const [IconRight, setIconRight] = useState<IconType | null>(null);
|
|
29
|
+
|
|
30
|
+
// Import icon dynamically
|
|
31
|
+
const loadIcon = useCallback(async (iconName?: string) => {
|
|
32
|
+
if (!iconName) return null;
|
|
33
|
+
try {
|
|
34
|
+
const module = await import('@heroicons/react/24/outline');
|
|
35
|
+
const Icon = module[iconName as keyof typeof module] as IconType;
|
|
36
|
+
return Icon || null;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error(`Failed to load icon ${iconName}:`, error);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const fetchIcons = async () => {
|
|
45
|
+
if (decoIcon) {
|
|
46
|
+
setIconLeft(await loadIcon(decoIcon));
|
|
47
|
+
}
|
|
48
|
+
if (actionIcon) {
|
|
49
|
+
setIconRight(await loadIcon(actionIcon));
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
fetchIcons();
|
|
53
|
+
}, [decoIcon, actionIcon, loadIcon]);
|
|
54
|
+
|
|
55
|
+
// Define classes for size
|
|
56
|
+
const sizeClasses = {
|
|
57
|
+
large: 'text-body1 p-2',
|
|
58
|
+
medium: 'text-body1 p-1',
|
|
59
|
+
small: 'text-body2 p-1',
|
|
60
|
+
}[size];
|
|
61
|
+
|
|
62
|
+
// Define icon size classes
|
|
63
|
+
const sizeIcon = {
|
|
64
|
+
large: 'w-6 h-6',
|
|
65
|
+
medium: 'w-5 h-5',
|
|
66
|
+
small: 'w-4 h-4',
|
|
67
|
+
}[size];
|
|
68
|
+
|
|
69
|
+
// Define classes for button types
|
|
70
|
+
const baseTypeClasses = {
|
|
71
|
+
primary: 'bg-light-accent-main dark:bg-dark-accent-main text-light-text-primary dark:text-dark-text-contrast',
|
|
72
|
+
secondary: 'bg-light-primary-main dark:bg-dark-primary-main text-light-text-contrast dark:text-dark-text-contrast',
|
|
73
|
+
tertiary: 'bg-light-background-accent200 dark:bg-dark-background-accent200 text-light-text-primary dark:text-dark-text-primary',
|
|
74
|
+
textOnly: 'text-light-text-primary dark:text-dark-text-primary',
|
|
75
|
+
}[type];
|
|
76
|
+
|
|
77
|
+
const hoverTypeClasses = {
|
|
78
|
+
primary: 'hover:bg-light-accent-dark hover:dark:bg-dark-accent-dark',
|
|
79
|
+
secondary: 'hover:bg-light-primary-dark dark:hover:bg-dark-primary-dark',
|
|
80
|
+
tertiary: 'hover:bg-light-background-accent300 hover:dark:bg-dark-background-accent300',
|
|
81
|
+
textOnly: 'hover:bg-light-background-accent100 hover:dark:bg-dark-background-accent100',
|
|
82
|
+
}[type];
|
|
83
|
+
|
|
84
|
+
// State classes
|
|
85
|
+
const stateClasses = {
|
|
86
|
+
enabled: 'cursor-pointer',
|
|
87
|
+
focused: 'ring-2 ring-offset-4 ring-offset-light-background-default dark:ring-offset-dark-background-default ring-light-accent-main dark:ring-dark-accent-main',
|
|
88
|
+
disabled: type === 'textOnly'
|
|
89
|
+
? 'cursor-not-allowed text-light-text-disabled dark:text-dark-text-disabled bg-transparent'
|
|
90
|
+
: 'cursor-not-allowed text-light-text-disabled dark:text-dark-text-disabled bg-light-actionBackground-disabled dark:bg-dark-actionBackground-disabled',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Build the button classes dynamically
|
|
94
|
+
const buttonClasses = `
|
|
95
|
+
flex flex-row space-x-2 items-center rounded-[8px]
|
|
96
|
+
${sizeClasses}
|
|
97
|
+
${state === 'enabled' ? baseTypeClasses : ''}
|
|
98
|
+
${state === 'focused' ? stateClasses.focused : ''}
|
|
99
|
+
${state === 'disabled' ? stateClasses.disabled : baseTypeClasses}
|
|
100
|
+
${state !== 'disabled' && isHovered ? hoverTypeClasses : ''}
|
|
101
|
+
`;
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<button
|
|
105
|
+
className={buttonClasses}
|
|
106
|
+
onMouseEnter={() => { if (state !== 'disabled') setIsHovered(true); }}
|
|
107
|
+
onMouseLeave={() => { if (state !== 'disabled') setIsHovered(false); }}
|
|
108
|
+
onClick={onClickButton} // Button click action
|
|
109
|
+
>
|
|
110
|
+
{IconLeft && (
|
|
111
|
+
<IconLeft className={sizeIcon} />
|
|
112
|
+
)}
|
|
113
|
+
<div className="whitespace-nowrap px-2">{label}</div>
|
|
114
|
+
{IconRight && (
|
|
115
|
+
<div onClick={onClickActionIcon} className="cursor-pointer">
|
|
116
|
+
<IconRight className={sizeIcon} />
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
</button>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
3
4
|
|
|
4
5
|
interface CheckboxProps {
|
|
5
6
|
label?: string;
|
|
@@ -12,7 +13,6 @@ export default function Checkbox({
|
|
|
12
13
|
checked = false,
|
|
13
14
|
onChange
|
|
14
15
|
}: CheckboxProps) {
|
|
15
|
-
|
|
16
16
|
const [isChecked, setIsChecked] = useState(checked);
|
|
17
17
|
|
|
18
18
|
const handleToggle = () => {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { ExclamationCircleIcon } from '@heroicons/react/24/outline';
|
|
4
|
+
|
|
5
|
+
interface EmptyBoxProps{
|
|
6
|
+
text: string;
|
|
7
|
+
size: "small" | "large";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function EmptyBox ({
|
|
11
|
+
text,
|
|
12
|
+
size,
|
|
13
|
+
}: EmptyBoxProps) {
|
|
14
|
+
|
|
15
|
+
const color = 'text-light-text-disabled dark:text-dark-text-disabled';
|
|
16
|
+
|
|
17
|
+
const height = size === 'small'
|
|
18
|
+
? 'py-6'
|
|
19
|
+
: 'h-full';
|
|
20
|
+
|
|
21
|
+
const iconSize = 'size-6' ;
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className={`flex flex-col space-y-2 justify-center items-center w-full ${height}`}>
|
|
25
|
+
<ExclamationCircleIcon
|
|
26
|
+
className={`${iconSize} ${color}`}
|
|
27
|
+
/>
|
|
28
|
+
<p className={`text-body1 ${color}`} text-center>
|
|
29
|
+
{text}
|
|
30
|
+
</p>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
export default function LoadingScreen() {
|
|
5
|
+
return (
|
|
6
|
+
<div className="flex justify-center items-center h-full w-full min-h-[200px]">
|
|
7
|
+
<div className="w-12 h-12 border-4 border-t-transparent border-light-accent-main rounded-full animate-spin"></div>
|
|
8
|
+
</div>
|
|
9
|
+
);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function LoadingSmall() {
|
|
13
|
+
return (
|
|
14
|
+
<div className="flex justify-center items-center h-[40px] w-[40px]">
|
|
15
|
+
<div className="w-[40px] h-[40px] border-4 border-t-transparent border-light-accent-main rounded-full animate-spin"></div>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
interface ModalProps {
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
title: string;
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
onClose: () => void;
|
|
8
|
+
actions: React.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function Modal({
|
|
12
|
+
isOpen,
|
|
13
|
+
title,
|
|
14
|
+
children,
|
|
15
|
+
onClose,
|
|
16
|
+
actions,
|
|
17
|
+
}: ModalProps) {
|
|
18
|
+
if (!isOpen) return null;
|
|
19
|
+
|
|
20
|
+
const handleOverlayClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
21
|
+
if (e.target === e.currentTarget) {
|
|
22
|
+
onClose();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
28
|
+
if (e.key === 'Escape') {
|
|
29
|
+
onClose();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
34
|
+
return () => {
|
|
35
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
36
|
+
};
|
|
37
|
+
}, [onClose]);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div
|
|
41
|
+
className="fixed inset-[-32px] bg-black bg-opacity-50 flex items-center justify-center z-50"
|
|
42
|
+
onClick={handleOverlayClick}
|
|
43
|
+
role="dialog"
|
|
44
|
+
aria-labelledby="modal-title"
|
|
45
|
+
aria-modal="true"
|
|
46
|
+
>
|
|
47
|
+
<div
|
|
48
|
+
className="bg-light-background-default dark:bg-dark-background-default p-6 rounded-[8px] space-y-4 w-[600px]"
|
|
49
|
+
style={{
|
|
50
|
+
maxHeight: 'calc(100vh - 64px)',
|
|
51
|
+
overflowY: 'auto',
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
<h2 id="modal-title" className="text-h6">
|
|
55
|
+
{title}
|
|
56
|
+
</h2>
|
|
57
|
+
<div className="text-body1 space-y-4">
|
|
58
|
+
{children}
|
|
59
|
+
</div>
|
|
60
|
+
<div className="flex justify-between">
|
|
61
|
+
{actions}
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React, { useState, useRef, useLayoutEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
interface PopoverProps {
|
|
5
|
+
id?: string;
|
|
6
|
+
anchorEl?: HTMLElement | null;
|
|
7
|
+
open?: boolean;
|
|
8
|
+
onClose?: () => void;
|
|
9
|
+
children: any;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function Popover({
|
|
13
|
+
anchorEl,
|
|
14
|
+
open = false,
|
|
15
|
+
onClose,
|
|
16
|
+
children
|
|
17
|
+
}: PopoverProps) {
|
|
18
|
+
|
|
19
|
+
const [popoverStyle, setPopoverStyle] = useState<React.CSSProperties>({});
|
|
20
|
+
const popoverRef = useRef<HTMLDivElement>(null);
|
|
21
|
+
|
|
22
|
+
// Close popover when clicking outside
|
|
23
|
+
useLayoutEffect(() => {
|
|
24
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
25
|
+
if (popoverRef.current && !popoverRef.current.contains(event.target as Node) && anchorEl) {
|
|
26
|
+
onClose?.(); // Safe call for optional onClose
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
if (open) {
|
|
30
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
31
|
+
}
|
|
32
|
+
return () => {
|
|
33
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
34
|
+
};
|
|
35
|
+
}, [open, anchorEl, onClose]);
|
|
36
|
+
|
|
37
|
+
// Calculate position and handle scroll/resize
|
|
38
|
+
useLayoutEffect(() => {
|
|
39
|
+
if (anchorEl && open) {
|
|
40
|
+
const handlePositioning = () => {
|
|
41
|
+
const anchorRect = anchorEl.getBoundingClientRect();
|
|
42
|
+
const popoverRect = popoverRef.current?.getBoundingClientRect();
|
|
43
|
+
|
|
44
|
+
if (popoverRect) {
|
|
45
|
+
const spaceBelow = window.innerHeight - anchorRect.bottom;
|
|
46
|
+
const spaceAbove = anchorRect.top;
|
|
47
|
+
|
|
48
|
+
// Decide whether to place the popover above or below the anchor
|
|
49
|
+
const shouldPlaceAbove = spaceBelow < popoverRect.height + 8 && spaceAbove > popoverRect.height + 8;
|
|
50
|
+
|
|
51
|
+
// Calculate top and left positions
|
|
52
|
+
const topPosition = shouldPlaceAbove
|
|
53
|
+
? anchorRect.top + window.scrollY - popoverRect.height - 8
|
|
54
|
+
: anchorRect.bottom + window.scrollY + 8;
|
|
55
|
+
|
|
56
|
+
let leftPosition = anchorRect.left + window.scrollX + (anchorRect.width / 2) - (popoverRect.width / 2);
|
|
57
|
+
|
|
58
|
+
// Ensure the popover doesn't overflow off the left or right of the screen
|
|
59
|
+
if (leftPosition < 8) {
|
|
60
|
+
leftPosition = 8; // Prevent overflow on the left
|
|
61
|
+
} else if (leftPosition + popoverRect.width > window.innerWidth - 8) {
|
|
62
|
+
leftPosition = window.innerWidth - popoverRect.width - 8; // Prevent overflow on the right
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
setPopoverStyle({
|
|
66
|
+
position: 'absolute',
|
|
67
|
+
top: `${topPosition}px`,
|
|
68
|
+
left: `${leftPosition}px`,
|
|
69
|
+
zIndex: 10000,
|
|
70
|
+
visibility: 'visible',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
handlePositioning(); // Initial positioning calculation
|
|
76
|
+
window.addEventListener('resize', handlePositioning);
|
|
77
|
+
window.addEventListener('scroll', handlePositioning, true);
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
window.removeEventListener('resize', handlePositioning);
|
|
81
|
+
window.removeEventListener('scroll', handlePositioning, true);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}, [anchorEl, open]);
|
|
85
|
+
|
|
86
|
+
if (!open || !anchorEl) return null;
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div
|
|
90
|
+
ref={popoverRef}
|
|
91
|
+
style={popoverStyle}
|
|
92
|
+
className="bg-light-background-accent100 dark:bg-dark-background-accent100 rounded-[8px] shadow-lg p-2"
|
|
93
|
+
role="dialog"
|
|
94
|
+
>
|
|
95
|
+
{children}
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -6,19 +6,36 @@ type TabProps = {
|
|
|
6
6
|
label: string;
|
|
7
7
|
href?: string;
|
|
8
8
|
isSelected: boolean;
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
onClickTab: () => void;
|
|
10
|
+
onClickActionIcon?: any;
|
|
11
|
+
decoIcon?: string;
|
|
12
|
+
actionIcon?: string;
|
|
11
13
|
};
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
const [Icon, setIcon] = useState<React.ComponentType<React.SVGProps<SVGSVGElement>> | null>(null);
|
|
15
|
+
type IconType = React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
|
15
16
|
|
|
17
|
+
export default function Tab({
|
|
18
|
+
label,
|
|
19
|
+
href,
|
|
20
|
+
isSelected,
|
|
21
|
+
onClickTab,
|
|
22
|
+
onClickActionIcon,
|
|
23
|
+
decoIcon,
|
|
24
|
+
actionIcon
|
|
25
|
+
}: TabProps) {
|
|
26
|
+
const router = useRouter();
|
|
27
|
+
const pathname = usePathname();
|
|
28
|
+
|
|
29
|
+
const [IconLeft, setIconLeft] = useState<IconType | null>(null);
|
|
30
|
+
const [IconRight, setIconRight] = useState<IconType | null>(null);
|
|
31
|
+
|
|
32
|
+
// Load icon dynamically
|
|
16
33
|
const loadIcon = useCallback(async (iconName?: string) => {
|
|
17
34
|
if (!iconName) return null;
|
|
18
35
|
try {
|
|
19
36
|
const module = await import('@heroicons/react/24/outline');
|
|
20
|
-
const
|
|
21
|
-
return
|
|
37
|
+
const Icon = module[iconName as keyof typeof module] as IconType;
|
|
38
|
+
return Icon || null;
|
|
22
39
|
} catch (error) {
|
|
23
40
|
console.error(`Failed to load icon ${iconName}:`, error);
|
|
24
41
|
return null;
|
|
@@ -26,23 +43,19 @@ const TabIcon: React.FC<{ iconName?: string }> = ({ iconName }) => {
|
|
|
26
43
|
}, []);
|
|
27
44
|
|
|
28
45
|
useEffect(() => {
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
31
|
-
|
|
46
|
+
const fetchIcons = async () => {
|
|
47
|
+
if (decoIcon) {
|
|
48
|
+
setIconLeft(await loadIcon(decoIcon));
|
|
49
|
+
}
|
|
50
|
+
if (actionIcon) {
|
|
51
|
+
setIconRight(await loadIcon(actionIcon));
|
|
32
52
|
}
|
|
33
53
|
};
|
|
34
|
-
|
|
35
|
-
}, [
|
|
36
|
-
|
|
37
|
-
return Icon ? <Icon className="w-5 h-5" /> : null;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const Tab: React.FC<TabProps> = ({ label, href, isSelected, onClick, iconName }) => {
|
|
41
|
-
const router = useRouter();
|
|
42
|
-
const pathname = usePathname();
|
|
54
|
+
fetchIcons();
|
|
55
|
+
}, [decoIcon, actionIcon, loadIcon]);
|
|
43
56
|
|
|
44
57
|
const handleClick = () => {
|
|
45
|
-
|
|
58
|
+
onClickTab();
|
|
46
59
|
if (href) {
|
|
47
60
|
router.push(href);
|
|
48
61
|
}
|
|
@@ -58,12 +71,15 @@ const Tab: React.FC<TabProps> = ({ label, href, isSelected, onClick, iconName })
|
|
|
58
71
|
`}
|
|
59
72
|
onClick={handleClick}
|
|
60
73
|
>
|
|
61
|
-
{
|
|
62
|
-
<span className="whitespace-nowrap text-body1">
|
|
74
|
+
{IconLeft && <IconLeft className="w-5 h-5" />}
|
|
75
|
+
<span className="whitespace-nowrap text-body1 px-2">
|
|
63
76
|
{label}
|
|
64
77
|
</span>
|
|
78
|
+
{IconRight && (
|
|
79
|
+
<div onClick={onClickActionIcon} className="cursor-pointer">
|
|
80
|
+
<IconRight className="w-5 h-5" />
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
65
83
|
</div>
|
|
66
84
|
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export default Tab;
|
|
85
|
+
}
|
|
@@ -27,7 +27,6 @@ export default function Tag({
|
|
|
27
27
|
onClick,
|
|
28
28
|
color = 'default',
|
|
29
29
|
}: TagProps) {
|
|
30
|
-
|
|
31
30
|
const [isHovered, setIsHovered] = useState(false);
|
|
32
31
|
const [Icon, setIcon] = useState<IconType | null>(null);
|
|
33
32
|
const [DeleteIcon, setDeleteIcon] = useState<IconType | null>(null);
|
|
@@ -103,6 +102,7 @@ export default function Tag({
|
|
|
103
102
|
>
|
|
104
103
|
{DeleteIcon && <DeleteIcon className="w-4 h-4" />}
|
|
105
104
|
</button>
|
|
105
|
+
|
|
106
106
|
)}
|
|
107
107
|
</div>
|
|
108
108
|
);
|
|
@@ -5,9 +5,13 @@ interface TextFieldProps {
|
|
|
5
5
|
id: string;
|
|
6
6
|
label?: string;
|
|
7
7
|
value: string;
|
|
8
|
-
onChange:
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;// Specify the event type for both input and textarea
|
|
9
|
+
onBlur?: (e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;// Handle blur event for input/textarea
|
|
10
|
+
onFocus?: (e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;// Handle focus event for input/textarea
|
|
11
|
+
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => void; // Add onKeyDown
|
|
12
|
+
autoFocus?: boolean;
|
|
13
|
+
iconLeft?: React.ReactNode;
|
|
14
|
+
iconRight?: React.ReactNode | React.ComponentType<any>;
|
|
11
15
|
multiline?: boolean;
|
|
12
16
|
maxRows?: number;
|
|
13
17
|
disabled?: boolean;
|
|
@@ -19,6 +23,10 @@ export default function TextField({
|
|
|
19
23
|
label,
|
|
20
24
|
value,
|
|
21
25
|
onChange,
|
|
26
|
+
onBlur,
|
|
27
|
+
onFocus,
|
|
28
|
+
onKeyDown,
|
|
29
|
+
autoFocus = false, // Accept the autoFocus prop with default value
|
|
22
30
|
iconLeft,
|
|
23
31
|
iconRight,
|
|
24
32
|
multiline = false,
|
|
@@ -26,47 +34,31 @@ export default function TextField({
|
|
|
26
34
|
disabled = false,
|
|
27
35
|
error = false,
|
|
28
36
|
}: TextFieldProps) {
|
|
29
|
-
|
|
30
37
|
const [isFocused, setIsFocused] = useState(false);
|
|
31
38
|
|
|
32
|
-
// Base styles
|
|
33
39
|
const baseClasses = 'w-full border rounded-[8px] p-2';
|
|
34
|
-
|
|
35
|
-
// Background color
|
|
36
40
|
const bgColor = 'bg-light-background-default dark:bg-dark-background-default transition-colors duration-300 ease-in-out';
|
|
37
|
-
|
|
38
|
-
// Border color
|
|
39
41
|
const borderColor = 'border-light-outlinedBorder-active dark:border-dark-outlinedBorder-active';
|
|
40
|
-
|
|
41
|
-
// State styles
|
|
42
|
-
const disabledClasses = 'bg-gray-200 cursor-not-allowed';
|
|
43
|
-
const errorClasses = 'border-red-500 focus:ring-red-500';
|
|
44
|
-
const focusClasses = 'focus:border-light-accent-main focus:dark:border-dark-accent-main outline-none';
|
|
45
|
-
const hoverClasses = 'hover:border-light-outlinedBorder-hover';
|
|
46
|
-
const defaultClasses = 'border-gray-300';
|
|
47
|
-
|
|
48
|
-
// Container styles
|
|
49
42
|
const containerClasses = `
|
|
50
43
|
${bgColor}
|
|
51
44
|
${borderColor}
|
|
52
45
|
${baseClasses}
|
|
53
|
-
${disabled ?
|
|
54
|
-
${error ?
|
|
55
|
-
${isFocused ?
|
|
56
|
-
${!disabled && !error ?
|
|
57
|
-
|
|
46
|
+
${disabled ? 'bg-gray-200 cursor-not-allowed' : ''}
|
|
47
|
+
${error ? 'border-red-500 focus:ring-red-500' : ''}
|
|
48
|
+
${isFocused ? 'focus:border-light-accent-main focus:dark:border-dark-accent-main outline-none' : ''}
|
|
49
|
+
${!disabled && !error ? 'hover:border-light-outlinedBorder-hover' : ''}
|
|
50
|
+
border-gray-300
|
|
58
51
|
`;
|
|
59
52
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
};
|
|
53
|
+
const renderIconRight = () => {
|
|
54
|
+
if (React.isValidElement(iconRight)) {
|
|
55
|
+
return iconRight;
|
|
56
|
+
}
|
|
57
|
+
if (typeof iconRight === 'function') {
|
|
58
|
+
return React.createElement(iconRight);
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
};
|
|
70
62
|
|
|
71
63
|
return (
|
|
72
64
|
<div className="flex flex-col w-full">
|
|
@@ -93,8 +85,16 @@ export default function TextField({
|
|
|
93
85
|
className={containerClasses}
|
|
94
86
|
value={value}
|
|
95
87
|
onChange={onChange}
|
|
96
|
-
onFocus={() =>
|
|
97
|
-
|
|
88
|
+
onFocus={(e) => {
|
|
89
|
+
setIsFocused(true);
|
|
90
|
+
if (onFocus) onFocus(e);
|
|
91
|
+
}}
|
|
92
|
+
onBlur={(e) => {
|
|
93
|
+
setIsFocused(false);
|
|
94
|
+
if (onBlur) onBlur(e);
|
|
95
|
+
}}
|
|
96
|
+
onKeyDown={onKeyDown}
|
|
97
|
+
autoFocus={autoFocus} // Pass autoFocus to textarea
|
|
98
98
|
disabled={disabled}
|
|
99
99
|
/>
|
|
100
100
|
) : (
|
|
@@ -104,8 +104,16 @@ export default function TextField({
|
|
|
104
104
|
className={containerClasses}
|
|
105
105
|
value={value}
|
|
106
106
|
onChange={onChange}
|
|
107
|
-
onFocus={() =>
|
|
108
|
-
|
|
107
|
+
onFocus={(e) => {
|
|
108
|
+
setIsFocused(true);
|
|
109
|
+
if (onFocus) onFocus(e);
|
|
110
|
+
}}
|
|
111
|
+
onBlur={(e) => {
|
|
112
|
+
setIsFocused(false);
|
|
113
|
+
if (onBlur) onBlur(e);
|
|
114
|
+
}}
|
|
115
|
+
onKeyDown={onKeyDown}
|
|
116
|
+
autoFocus={autoFocus} // Pass autoFocus to input
|
|
109
117
|
disabled={disabled}
|
|
110
118
|
/>
|
|
111
119
|
)}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
interface TimeStampProps{
|
|
5
|
+
createdAt: string | number | Date;
|
|
6
|
+
dateFormat: 'absolute' | 'relative';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function TimeStamp({
|
|
10
|
+
createdAt,
|
|
11
|
+
dateFormat,
|
|
12
|
+
}: TimeStampProps) {
|
|
13
|
+
|
|
14
|
+
// absolute timestamp
|
|
15
|
+
const absoluteTimeStamp = (createdAt: string | number | Date): string => {
|
|
16
|
+
const date = new Date(createdAt);
|
|
17
|
+
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
18
|
+
const day = date.getDate().toString().padStart(2, '0');
|
|
19
|
+
const year = date.getFullYear();
|
|
20
|
+
const hours = date.getHours();
|
|
21
|
+
const minutes = date.getMinutes();
|
|
22
|
+
const formattedHours = hours.toString().padStart(2, '0');
|
|
23
|
+
const formattedMinutes = minutes.toString().padStart(2, '0');
|
|
24
|
+
const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
25
|
+
const dayOfWeek = daysOfWeek[date.getDay()];
|
|
26
|
+
return `${month}/${day}/${year} ${dayOfWeek} ${formattedHours}:${formattedMinutes}`;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// relative timestamp
|
|
30
|
+
const relativeTimeStamp = (createdAt: string | number | Date): string => {
|
|
31
|
+
const date = new Date(createdAt);
|
|
32
|
+
const now = new Date();
|
|
33
|
+
const diff = now.getTime() - date.getTime();
|
|
34
|
+
const seconds = Math.floor(diff / 1000);
|
|
35
|
+
const minutes = Math.floor(seconds / 60);
|
|
36
|
+
const hours = Math.floor(minutes / 60);
|
|
37
|
+
const days = Math.floor(hours / 24);
|
|
38
|
+
const weeks = Math.floor(days / 7);
|
|
39
|
+
const months = Math.floor(days / 30);
|
|
40
|
+
const years = Math.floor(days / 365);
|
|
41
|
+
if (years > 0) {
|
|
42
|
+
return `${years} year${years > 1 ? 's' : ''} ago`;
|
|
43
|
+
} else if (months > 0) {
|
|
44
|
+
return `${months} month${months > 1 ? 's' : ''} ago`;
|
|
45
|
+
} else if (weeks > 0) {
|
|
46
|
+
return `${weeks} week${weeks > 1 ? 's' : ''} ago`;
|
|
47
|
+
} else if (days > 0) {
|
|
48
|
+
return `${days} day${days > 1 ? 's' : ''} ago`;
|
|
49
|
+
} else if (hours > 0) {
|
|
50
|
+
return `${hours} hour${hours > 1 ? 's' : ''} ago`;
|
|
51
|
+
} else if (minutes > 0) {
|
|
52
|
+
return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
|
|
53
|
+
} else {
|
|
54
|
+
return `${seconds} second${seconds > 1 ? 's' : ''} ago`;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const timeStamp = dateFormat === 'absolute' ? absoluteTimeStamp(createdAt) : relativeTimeStamp(createdAt);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<p className="text-caption text-light-text-secondary dark:text-dark-text-secondary">
|
|
62
|
+
{timeStamp}
|
|
63
|
+
</p>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
@@ -6,11 +6,7 @@ interface TooltipProps {
|
|
|
6
6
|
children: React.ReactElement;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export default function Tooltip({
|
|
10
|
-
title,
|
|
11
|
-
children
|
|
12
|
-
}: TooltipProps) {
|
|
13
|
-
|
|
9
|
+
export default function Tooltip({ title, children }: TooltipProps) {
|
|
14
10
|
const [visible, setVisible] = useState(false);
|
|
15
11
|
const [position, setPosition] = useState<'top' | 'bottom'>('bottom');
|
|
16
12
|
const tooltipRef = useRef<HTMLDivElement>(null);
|
package/index.css
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
@tailwind base;
|
|
2
2
|
@tailwind components;
|
|
3
3
|
@tailwind utilities;
|
|
4
|
-
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500&display=swap');
|
|
4
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500&display=swap');
|
|
5
|
+
|
|
6
|
+
body {
|
|
7
|
+
font-family: 'Inter', sans-serif;
|
|
8
|
+
}
|
package/index.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import './index.css';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export * from './components/
|
|
5
|
-
export * from './components/
|
|
6
|
-
export * from './components/
|
|
7
|
-
export * from './components/
|
|
8
|
-
export * from './components/
|
|
9
|
-
export * from './components/
|
|
10
|
-
export * from './components/
|
|
11
|
-
export * from './components/
|
|
12
|
-
export * from './components/
|
|
13
|
-
export * from './components/
|
|
14
|
-
export * from './components/
|
|
2
|
+
import './designTokens';
|
|
3
|
+
|
|
4
|
+
export * from './components/alert';
|
|
5
|
+
export * from './components/button';
|
|
6
|
+
export * from './components/checkBox';
|
|
7
|
+
export * from './components/emptyBox';
|
|
8
|
+
export * from './components/iconButton';
|
|
9
|
+
export * from './components/loadingScreen';
|
|
10
|
+
export * from './components/menuItem';
|
|
11
|
+
export * from './components/modal';
|
|
12
|
+
export * from './components/popover';
|
|
13
|
+
export * from './components/tab';
|
|
14
|
+
export * from './components/tag';
|
|
15
|
+
export * from './components/textField';
|
|
16
|
+
export * from './components/timeStamp'
|
|
17
|
+
export * from './components/tooltip';
|
|
18
|
+
export * from './components/userImage';
|
package/package.json
CHANGED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
3
|
-
import * as HeroIcons from '@heroicons/react/24/outline';
|
|
4
|
-
|
|
5
|
-
interface ButtonProps {
|
|
6
|
-
size: 'large' | 'medium' | 'small';
|
|
7
|
-
type: 'primary' | 'secondary' | 'tertiary' | 'textOnly';
|
|
8
|
-
state: 'enabled' | 'hovered' | 'selected' | 'focused' | 'disabled';
|
|
9
|
-
label: string;
|
|
10
|
-
iconLeftName?: keyof typeof HeroIcons; // Reference to HeroIcons
|
|
11
|
-
iconRightName?: keyof typeof HeroIcons; // Reference to HeroIcons
|
|
12
|
-
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type IconType = React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
|
16
|
-
|
|
17
|
-
export default function Button({
|
|
18
|
-
size,
|
|
19
|
-
type,
|
|
20
|
-
state,
|
|
21
|
-
label,
|
|
22
|
-
iconLeftName,
|
|
23
|
-
iconRightName,
|
|
24
|
-
onClick,
|
|
25
|
-
}: ButtonProps) {
|
|
26
|
-
|
|
27
|
-
const [isHovered, setIsHovered] = useState(false);
|
|
28
|
-
const [IconLeft, setIconLeft] = useState<React.ComponentType<React.SVGProps<SVGSVGElement>> | null>(null);
|
|
29
|
-
const [IconRight, setIconRight] = useState<React.ComponentType<React.SVGProps<SVGSVGElement>> | null>(null);
|
|
30
|
-
|
|
31
|
-
// import icon
|
|
32
|
-
const loadIcon = useCallback(async (iconName?: string) => {
|
|
33
|
-
if (!iconName) return null;
|
|
34
|
-
try {
|
|
35
|
-
const module = await import('@heroicons/react/24/outline');
|
|
36
|
-
const Icon = module[iconName as keyof typeof module] as IconType;
|
|
37
|
-
return Icon || null;
|
|
38
|
-
} catch (error) {
|
|
39
|
-
console.error(`Failed to load icon ${iconName}:`, error);
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
}, []);
|
|
43
|
-
|
|
44
|
-
// Load icons on mount and when props change
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
const fetchIcons = async () => {
|
|
47
|
-
if (typeof iconLeftName === 'string') {
|
|
48
|
-
setIconLeft(await loadIcon(iconLeftName));
|
|
49
|
-
}
|
|
50
|
-
if (typeof iconRightName === 'string') {
|
|
51
|
-
setIconRight(await loadIcon(iconRightName));
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
fetchIcons();
|
|
55
|
-
}, [iconLeftName, iconRightName, loadIcon]);
|
|
56
|
-
|
|
57
|
-
// size
|
|
58
|
-
const sizeClasses =
|
|
59
|
-
size === 'large' ? 'text-body1 p-2'
|
|
60
|
-
: size === 'small' ? 'text-body2 p-1'
|
|
61
|
-
: 'text-body1 p-1'; // medium size
|
|
62
|
-
|
|
63
|
-
// icon size
|
|
64
|
-
const sizeIcon =
|
|
65
|
-
size === 'large' ? 'w-6 h-6'
|
|
66
|
-
: size === 'small' ? 'w-4 h-4'
|
|
67
|
-
: 'w-5 h-5'; // medium size
|
|
68
|
-
|
|
69
|
-
// type
|
|
70
|
-
const typeClasses = {
|
|
71
|
-
primary: 'bg-light-accent-main dark:bg-dark-accent-main text-light-text-primary dark:text-dark-text-contrast',
|
|
72
|
-
secondary: 'bg-light-primary-main dark:bg-dark-primary-main text-light-text-contrast dark:text-dark-text-contrast',
|
|
73
|
-
tertiary: 'bg-light-background-accent200 dark:bg-dark-background-accent200 text-light-text-primary dark:text-dark-text-primary',
|
|
74
|
-
textOnly: 'text-light-text-primary dark:text-dark-text-primary',
|
|
75
|
-
}[type];
|
|
76
|
-
|
|
77
|
-
const hoverTypeClasses = {
|
|
78
|
-
primary: ' hover:bg-light-accent-dark hover:dark:bg-dark-accent-dark',
|
|
79
|
-
secondary: 'hover:bg-light-primary-dark dark:hover:bg-dark-primary-dark',
|
|
80
|
-
tertiary: 'hover:bg-light-background-accent300 hover:dark:bg-dark-background-accent300',
|
|
81
|
-
textOnly: 'hover:bg-light-background-accent100 hover:dark:bg-dark-background-accent100',
|
|
82
|
-
}[type];
|
|
83
|
-
|
|
84
|
-
// state
|
|
85
|
-
const stateClasses = {
|
|
86
|
-
enabled: 'cursor-pointer',
|
|
87
|
-
hovered: 'cursor-pointer',
|
|
88
|
-
selected: 'bg-light-primary-main dark:bg-dark-primary-main text-light-text-contrast dark:text-dark-text-contrast',
|
|
89
|
-
focused: 'ring-2 ring-light-accent-main dark:ring-dark-accent-main',
|
|
90
|
-
disabled: 'text-light-text-disabled dark:text-dark-text-disabled bg-light-actionBackground-disabled dark:bg-dark-actionBackground-disabled',
|
|
91
|
-
}[state];
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return (
|
|
96
|
-
<button
|
|
97
|
-
className={`flex flex-row space-x-2 items-center rounded-[8px]
|
|
98
|
-
${sizeClasses}
|
|
99
|
-
${typeClasses}
|
|
100
|
-
${state !== 'disabled' && isHovered ? hoverTypeClasses : ''}
|
|
101
|
-
${stateClasses}`}
|
|
102
|
-
disabled={state === 'disabled'}
|
|
103
|
-
onMouseEnter={() => {
|
|
104
|
-
if (state !== 'disabled') setIsHovered(true);
|
|
105
|
-
}}
|
|
106
|
-
onMouseLeave={() => {
|
|
107
|
-
if (state !== 'disabled') setIsHovered(false);
|
|
108
|
-
}}
|
|
109
|
-
onClick={onClick}
|
|
110
|
-
>
|
|
111
|
-
{IconLeft && <IconLeft className={sizeIcon} />}
|
|
112
|
-
<div className="whitespace-nowrap px-2">
|
|
113
|
-
{label}
|
|
114
|
-
</div>
|
|
115
|
-
{IconRight && <IconRight className={sizeIcon} />}
|
|
116
|
-
</button>
|
|
117
|
-
);
|
|
118
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
|
|
4
|
-
interface ModalProps {
|
|
5
|
-
isOpen: boolean;
|
|
6
|
-
title: string;
|
|
7
|
-
children: React.ReactNode;
|
|
8
|
-
onClose: () => void;
|
|
9
|
-
actions?: React.ReactNode;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export default function Modal({
|
|
13
|
-
isOpen,
|
|
14
|
-
title,
|
|
15
|
-
children,
|
|
16
|
-
onClose,
|
|
17
|
-
actions,
|
|
18
|
-
}: ModalProps) {
|
|
19
|
-
|
|
20
|
-
if (!isOpen) return null;
|
|
21
|
-
|
|
22
|
-
const handleOverlayClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
23
|
-
if (e.target === e.currentTarget) {
|
|
24
|
-
onClose();
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
return (
|
|
29
|
-
<div className="fixed inset-[-32px] bg-black bg-opacity-50 flex items-center justify-center z-50 "
|
|
30
|
-
onClick={handleOverlayClick}
|
|
31
|
-
>
|
|
32
|
-
<div className="bg-light-background-default dark:bg-dark-background-default p-6 rounded-[8px] space-y-4 w-[600px]"
|
|
33
|
-
style={{
|
|
34
|
-
position: 'relative',
|
|
35
|
-
margin: '0 auto'
|
|
36
|
-
}}>
|
|
37
|
-
<h2 className="text-h6">
|
|
38
|
-
{title}
|
|
39
|
-
</h2>
|
|
40
|
-
<div className="text-body1 space-y-4">
|
|
41
|
-
{children}
|
|
42
|
-
</div>
|
|
43
|
-
<div className="flex justify-between">
|
|
44
|
-
{actions}
|
|
45
|
-
</div>
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import React, { useState, useRef, useEffect } from 'react';
|
|
3
|
-
|
|
4
|
-
interface PopoverProps {
|
|
5
|
-
id?: string;
|
|
6
|
-
anchorEl: HTMLElement | null;
|
|
7
|
-
open: boolean;
|
|
8
|
-
onClose: () => void;
|
|
9
|
-
children: any;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export default function Popover({
|
|
13
|
-
anchorEl,
|
|
14
|
-
open,
|
|
15
|
-
onClose,
|
|
16
|
-
children
|
|
17
|
-
}: PopoverProps) {
|
|
18
|
-
|
|
19
|
-
const [popoverStyle, setPopoverStyle] = useState<React.CSSProperties>({});
|
|
20
|
-
const popoverRef = useRef<HTMLDivElement>(null);
|
|
21
|
-
|
|
22
|
-
// Close popover when clicking outside
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
const handleClickOutside = (event: MouseEvent) => {
|
|
25
|
-
if (popoverRef.current && !popoverRef.current.contains(event.target as Node) && anchorEl) {
|
|
26
|
-
onClose();
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
if (open) {
|
|
30
|
-
document.addEventListener('mousedown', handleClickOutside);
|
|
31
|
-
}
|
|
32
|
-
return () => {
|
|
33
|
-
document.removeEventListener('mousedown', handleClickOutside);
|
|
34
|
-
};
|
|
35
|
-
}, [open, anchorEl, onClose]);
|
|
36
|
-
|
|
37
|
-
// Calculate position
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
if (anchorEl && open) {
|
|
40
|
-
const anchorRect = anchorEl.getBoundingClientRect();
|
|
41
|
-
const popoverRect = popoverRef.current?.getBoundingClientRect();
|
|
42
|
-
if (popoverRect) {
|
|
43
|
-
const spaceBelow = window.innerHeight - anchorRect.bottom;
|
|
44
|
-
const spaceAbove = anchorRect.top;
|
|
45
|
-
|
|
46
|
-
// Determine if we should place the popover above
|
|
47
|
-
const shouldPlaceAbove = spaceBelow < popoverRect.height + 8 && spaceAbove > popoverRect.height + 8;
|
|
48
|
-
|
|
49
|
-
// Calculate top position, correcting for popover height when placing above
|
|
50
|
-
const topPosition = shouldPlaceAbove
|
|
51
|
-
? anchorRect.top + window.scrollY - popoverRect.height - 8 // Adjust by 8px to maintain spacing
|
|
52
|
-
: anchorRect.bottom + window.scrollY + 8; // Add 8px for spacing below
|
|
53
|
-
|
|
54
|
-
// Calculate left position with viewport boundary checks
|
|
55
|
-
let leftPosition = anchorRect.left + (anchorRect.width / 2) - (popoverRect.width / 2);
|
|
56
|
-
|
|
57
|
-
if (leftPosition < 8) {
|
|
58
|
-
// Adjust to align with the left boundary of the viewport
|
|
59
|
-
leftPosition = 8;
|
|
60
|
-
} else if (leftPosition + popoverRect.width > window.innerWidth - 8) {
|
|
61
|
-
// Adjust to align with the right boundary of the viewport
|
|
62
|
-
leftPosition = window.innerWidth - popoverRect.width - 8;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
setPopoverStyle({
|
|
66
|
-
position: 'absolute',
|
|
67
|
-
top: topPosition,
|
|
68
|
-
left: leftPosition,
|
|
69
|
-
zIndex: 10000,
|
|
70
|
-
pointerEvents: 'auto',
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}, [anchorEl, open]);
|
|
75
|
-
|
|
76
|
-
if (!open || !anchorEl) return null;
|
|
77
|
-
|
|
78
|
-
return (
|
|
79
|
-
<div
|
|
80
|
-
ref={popoverRef}
|
|
81
|
-
style={popoverStyle}
|
|
82
|
-
className="bg-light-background-default dark:bg-dark-background-default border rounded-[8px] shadow-lg"
|
|
83
|
-
role="dialog"
|
|
84
|
-
>
|
|
85
|
-
{children}
|
|
86
|
-
</div>
|
|
87
|
-
);
|
|
88
|
-
}
|
package/global.d.ts
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
// types/global.d.ts
|
|
2
|
-
export {}
|
|
3
|
-
declare global {
|
|
4
|
-
|
|
5
|
-
interface CommentReactionProps {
|
|
6
|
-
reactions: any;
|
|
7
|
-
isArchiveComment?: boolean;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface TimeStampProps{
|
|
11
|
-
createdAt: string | number | Date;
|
|
12
|
-
dateFormat: 'absolute' | 'relative';
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface CheckboxProps {
|
|
16
|
-
label?: string;
|
|
17
|
-
checked?: boolean;
|
|
18
|
-
onChange?: (checked: boolean) => void;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface ReactionIconButtonProps {
|
|
22
|
-
size?: "small" | "medium" | "large";
|
|
23
|
-
color?: "inherit" | "default" | "primary" | "secondary" | "error" | "info" | "success" | "warning";
|
|
24
|
-
figmaCommentId: string;
|
|
25
|
-
fileKey?: string;
|
|
26
|
-
accountId: string;
|
|
27
|
-
selectedHeadCommentId?: any;
|
|
28
|
-
associatedAccountId?: any;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface CheckIconButtonProps {
|
|
32
|
-
size: "small" | "medium" | "large";
|
|
33
|
-
color?: "inherit" | "default" | "primary" | "secondary" | "error" | "info" | "success" | "warning";
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface UncheckIconButtonProps {
|
|
37
|
-
size: "small" | "medium" | "large";
|
|
38
|
-
color?: "inherit" | "default" | "primary" | "secondary" | "error" | "info" | "success" | "warning";
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
interface ButtonProps{
|
|
42
|
-
size: 'large' | 'medium' | 'small';
|
|
43
|
-
type: 'primary' | 'secondary' | 'tertiary' | 'textOnly';
|
|
44
|
-
state: 'enabled' | 'hovered' | 'selected' | 'focused' | 'disabled';
|
|
45
|
-
label: string;
|
|
46
|
-
iconLeftName?: keyof typeof HeroIcons;
|
|
47
|
-
iconRightName?: keyof typeof HeroIcons;
|
|
48
|
-
onClick?: any;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
interface IconButtonProps{
|
|
52
|
-
variant: "contained" | "iconOnly";
|
|
53
|
-
color: "primary" | "secondary";
|
|
54
|
-
state: "enabled" | "selected" | "disabled";
|
|
55
|
-
iconName: keyof typeof HeroIcons;
|
|
56
|
-
onClick?: any;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
interface TagProps{
|
|
60
|
-
key?: any;
|
|
61
|
-
variant: "contained" | "textOnly";
|
|
62
|
-
size: "medium" | "small";
|
|
63
|
-
state?: "enabled" | "selected" ;
|
|
64
|
-
label: any;
|
|
65
|
-
iconName?: keyof typeof HeroIcons;
|
|
66
|
-
isDeletable?: keyof typeof HeroIcons;
|
|
67
|
-
onClick?: any;
|
|
68
|
-
color?: 'default' | 'info';
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
interface EmptyBoxProps{
|
|
72
|
-
text: string;
|
|
73
|
-
size: "small" | "large";
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
interface TabPanelProps {
|
|
77
|
-
children?: React.ReactNode;
|
|
78
|
-
index: number;
|
|
79
|
-
value: number;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
interface TextFieldProps {
|
|
83
|
-
id: string;
|
|
84
|
-
label?: string;
|
|
85
|
-
value: string;
|
|
86
|
-
onChange: any;
|
|
87
|
-
iconLeft?: any;
|
|
88
|
-
iconRight?: React.ReactNode | React.ComponentType<any>;
|
|
89
|
-
multiline?: boolean;
|
|
90
|
-
maxRows?: number;
|
|
91
|
-
disabled?: boolean;
|
|
92
|
-
error?: boolean;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
interface ModalProps {
|
|
96
|
-
isOpen: boolean;
|
|
97
|
-
title: string;
|
|
98
|
-
children: React.ReactNode;
|
|
99
|
-
onClose: () => void;
|
|
100
|
-
actions?: React.ReactNode;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
interface MenuItemProps {
|
|
104
|
-
href?: string;
|
|
105
|
-
iconName?: string;
|
|
106
|
-
label: string;
|
|
107
|
-
isSelected?: boolean;
|
|
108
|
-
onClick: any;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
type IconType = React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
|
112
|
-
|
|
113
|
-
interface TooltipProps {
|
|
114
|
-
title: string;
|
|
115
|
-
children: React.ReactElement;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
interface UserImageProps {
|
|
119
|
-
userHandle: string;
|
|
120
|
-
userImgUrl?: string;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
interface AlertProps {
|
|
124
|
-
open: boolean;
|
|
125
|
-
type: 'success' | 'warning' | 'error' | 'info';
|
|
126
|
-
message: string;
|
|
127
|
-
onClose: () => void;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
interface PopoverProps {
|
|
131
|
-
id?: string;
|
|
132
|
-
anchorEl: HTMLElement | null;
|
|
133
|
-
open: boolean;
|
|
134
|
-
onClose: () => void;
|
|
135
|
-
children: ReactNode;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
interface ScrapTagProps{
|
|
139
|
-
comment?: any;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
type TabProps = {
|
|
143
|
-
label: string;
|
|
144
|
-
href?: string;
|
|
145
|
-
isSelected: boolean;
|
|
146
|
-
onClick: () => void;
|
|
147
|
-
iconName?: string;
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
declare module '@heroicons/react/*' {
|
|
153
|
-
const content: any;
|
|
154
|
-
export default content;
|
|
155
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|