oneslash-design-system 1.2.2 → 1.2.4
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/components/alert.d.ts +3 -2
- package/dist/components/alert.jsx +69 -14
- package/dist/output.css +37 -12
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +4 -1
- package/.claude/settings.local.json +0 -9
- package/.eslintrc.json +0 -3
- package/components/alert.tsx +0 -55
- package/components/button.tsx +0 -120
- package/components/checkBox.tsx +0 -60
- package/components/emptyBox.tsx +0 -33
- package/components/iconButton.tsx +0 -103
- package/components/loadingScreen.tsx +0 -30
- package/components/menu.tsx +0 -35
- package/components/menuItem.tsx +0 -80
- package/components/modal.tsx +0 -85
- package/components/navigation.tsx +0 -27
- package/components/popover.tsx +0 -69
- package/components/radioGroup.tsx +0 -50
- package/components/select.tsx +0 -253
- package/components/tab.tsx +0 -85
- package/components/tableCell.tsx +0 -15
- package/components/tableContainer.tsx +0 -15
- package/components/tableHeader.tsx +0 -15
- package/components/tableHeaderCell.tsx +0 -15
- package/components/tableRow.tsx +0 -15
- package/components/tabsContainer.tsx +0 -23
- package/components/tag.tsx +0 -81
- package/components/textField.tsx +0 -116
- package/components/textarea.tsx +0 -120
- package/components/timeStamp.tsx +0 -65
- package/components/tooltip.tsx +0 -66
- package/components/userImage.tsx +0 -64
- package/designTokens.js +0 -234
- package/index.css +0 -8
- package/index.ts +0 -21
- package/next.config.mjs +0 -4
- package/oneslash-design-system-1.1.1.tgz +0 -0
- package/oneslash-design-system-1.1.30.tgz +0 -0
- package/oneslash-design-system-1.1.31.tgz +0 -0
- package/oneslash-design-system-1.1.33.tgz +0 -0
- package/postcss.config.mjs +0 -8
- package/tailwind.config.ts +0 -232
- package/tsconfig.json +0 -37
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oneslash-design-system",
|
|
3
3
|
"description": "A design system for the Oneslash projects",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.4",
|
|
5
5
|
"private": false,
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
8
11
|
"scripts": {
|
|
9
12
|
"dev": "next dev",
|
|
10
13
|
"build": "tsc && postcss index.css -o dist/output.css",
|
package/.eslintrc.json
DELETED
package/components/alert.tsx
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import React, { useEffect } from 'react';
|
|
3
|
-
|
|
4
|
-
interface AlertProps {
|
|
5
|
-
open?: boolean;
|
|
6
|
-
type: 'success' | 'warning' | 'error' | 'info';
|
|
7
|
-
message: string;
|
|
8
|
-
onClose: () => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export default function Alert({
|
|
12
|
-
open,
|
|
13
|
-
type,
|
|
14
|
-
message,
|
|
15
|
-
onClose
|
|
16
|
-
}: AlertProps) {
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
if (open) {
|
|
20
|
-
const timer = setTimeout(() => {
|
|
21
|
-
onClose();
|
|
22
|
-
}, 3000);
|
|
23
|
-
return () => clearTimeout(timer);
|
|
24
|
-
}
|
|
25
|
-
}, [open, onClose]);
|
|
26
|
-
|
|
27
|
-
if (!open) return null;
|
|
28
|
-
|
|
29
|
-
let bgColor;
|
|
30
|
-
switch (type) {
|
|
31
|
-
case 'error':
|
|
32
|
-
bgColor = 'bg-light-error-main dark:bg-dark-error-main';
|
|
33
|
-
break;
|
|
34
|
-
case 'warning':
|
|
35
|
-
bgColor = 'bg-light-warning-main dark:bg-dark-warning-main';
|
|
36
|
-
break;
|
|
37
|
-
case 'info':
|
|
38
|
-
bgColor = 'bg-light-info-main dark:bg-dark-info-main';
|
|
39
|
-
break;
|
|
40
|
-
case 'success':
|
|
41
|
-
bgColor = 'bg-light-success-main dark:bg-dark-success-main';
|
|
42
|
-
break;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<div className="fixed top-4 inset-x-0 z-50 flex justify-center">
|
|
47
|
-
<div className={`flex items-center justify-between w-full max-w-md p-4 rounded-[8px] shadow-lg text-light-text-contrast dark:text-dark-text-contrast ${bgColor}`}>
|
|
48
|
-
<span>{message}</span>
|
|
49
|
-
<button onClick={onClose} className="ml-4 text-xl font-bold">
|
|
50
|
-
×
|
|
51
|
-
</button>
|
|
52
|
-
</div>
|
|
53
|
-
</div>
|
|
54
|
-
);
|
|
55
|
-
}
|
package/components/button.tsx
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
3
|
-
|
|
4
|
-
interface ButtonProps {
|
|
5
|
-
size: 'small' | 'medium' | 'large';
|
|
6
|
-
type: 'primary' | 'secondary' | 'tertiary' | 'textOnly';
|
|
7
|
-
state: 'enabled' | 'hovered' | 'focused' | 'disabled';
|
|
8
|
-
label: string;
|
|
9
|
-
decoIcon?: string;
|
|
10
|
-
actionIcon?: string;
|
|
11
|
-
onClickButton?: any;
|
|
12
|
-
onClickActionIcon?: () => void;
|
|
13
|
-
className?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
type IconType = React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
|
17
|
-
|
|
18
|
-
export default function Button({
|
|
19
|
-
size,
|
|
20
|
-
type,
|
|
21
|
-
state,
|
|
22
|
-
label,
|
|
23
|
-
decoIcon,
|
|
24
|
-
actionIcon,
|
|
25
|
-
onClickButton,
|
|
26
|
-
onClickActionIcon,
|
|
27
|
-
className = '',
|
|
28
|
-
}: ButtonProps) {
|
|
29
|
-
const [isHovered, setIsHovered] = useState(false);
|
|
30
|
-
const [IconLeft, setIconLeft] = useState<IconType | null>(null);
|
|
31
|
-
const [IconRight, setIconRight] = useState<IconType | null>(null);
|
|
32
|
-
|
|
33
|
-
const loadIcon = useCallback(async (iconName?: string) => {
|
|
34
|
-
if (!iconName) return null;
|
|
35
|
-
try {
|
|
36
|
-
const module = await import('@heroicons/react/24/outline');
|
|
37
|
-
const Icon = module[iconName as keyof typeof module] as IconType;
|
|
38
|
-
return Icon || null;
|
|
39
|
-
} catch (error) {
|
|
40
|
-
console.error(`Failed to load icon ${iconName}:`, error);
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}, []);
|
|
44
|
-
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
const fetchIcons = async () => {
|
|
47
|
-
if (decoIcon) {
|
|
48
|
-
setIconLeft(await loadIcon(decoIcon));
|
|
49
|
-
}
|
|
50
|
-
if (actionIcon) {
|
|
51
|
-
setIconRight(await loadIcon(actionIcon));
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
fetchIcons();
|
|
55
|
-
}, [decoIcon, actionIcon, loadIcon]);
|
|
56
|
-
|
|
57
|
-
const sizeClasses = {
|
|
58
|
-
large: 'text-body1 p-2',
|
|
59
|
-
medium: 'text-body1 p-1',
|
|
60
|
-
small: 'text-body2 p-1',
|
|
61
|
-
}[size];
|
|
62
|
-
|
|
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
|
-
const baseTypeClasses = {
|
|
70
|
-
primary: 'bg-light-accent-main dark:bg-dark-accent-main text-light-text-primary dark:text-dark-text-contrast',
|
|
71
|
-
secondary: 'bg-light-secondary-main dark:bg-dark-secondary-main text-light-text-primary dark:text-dark-text-primary',
|
|
72
|
-
tertiary: 'bg-light-background-accent100 dark:bg-dark-background-accent100 text-light-text-primary dark:text-dark-text-primary',
|
|
73
|
-
textOnly: 'text-light-text-primary dark:text-dark-text-primary',
|
|
74
|
-
}[type];
|
|
75
|
-
|
|
76
|
-
const hoverTypeClasses = {
|
|
77
|
-
primary: 'hover:bg-light-accent-dark hover:dark:bg-dark-accent-dark',
|
|
78
|
-
secondary: 'hover:bg-light-secondary-dark dark:hover:bg-dark-secondary-dark',
|
|
79
|
-
tertiary: 'hover:bg-light-background-accent200 hover:dark:bg-dark-background-accent200',
|
|
80
|
-
textOnly: 'hover:bg-light-background-accent100 hover:dark:bg-dark-background-accent100',
|
|
81
|
-
}[type];
|
|
82
|
-
|
|
83
|
-
const stateClasses = {
|
|
84
|
-
enabled: 'cursor-pointer',
|
|
85
|
-
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',
|
|
86
|
-
disabled: type === 'textOnly'
|
|
87
|
-
? 'cursor-not-allowed text-light-text-disabled dark:text-dark-text-disabled bg-transparent'
|
|
88
|
-
: 'cursor-not-allowed text-light-text-disabled dark:text-dark-text-disabled bg-light-actionBackground-disabled dark:bg-dark-actionBackground-disabled',
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const buttonClasses = `
|
|
92
|
-
flex flex-row items-center rounded-[8px] transition-colors duration-200 ease-in-out justify-between
|
|
93
|
-
${sizeClasses}
|
|
94
|
-
${state === 'enabled' ? baseTypeClasses : ''}
|
|
95
|
-
${state === 'focused' ? stateClasses.focused : ''}
|
|
96
|
-
${state === 'disabled' ? stateClasses.disabled : baseTypeClasses}
|
|
97
|
-
${state !== 'disabled' && isHovered ? hoverTypeClasses : ''}
|
|
98
|
-
${className}
|
|
99
|
-
`;
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<button
|
|
103
|
-
className={buttonClasses}
|
|
104
|
-
onMouseEnter={() => { if (state !== 'disabled') setIsHovered(true); }}
|
|
105
|
-
onMouseLeave={() => { if (state !== 'disabled') setIsHovered(false); }}
|
|
106
|
-
onClick={onClickButton}
|
|
107
|
-
>
|
|
108
|
-
{/* Group IconLeft and label in a flex container for left alignment */}
|
|
109
|
-
<div className="flex items-center">
|
|
110
|
-
{IconLeft && <IconLeft className={sizeIcon} />}
|
|
111
|
-
<div className="whitespace-nowrap overflow-hidden truncate px-2">{label}</div>
|
|
112
|
-
</div>
|
|
113
|
-
{IconRight && (
|
|
114
|
-
<div onClick={onClickActionIcon} className="cursor-pointer">
|
|
115
|
-
<IconRight className={sizeIcon} />
|
|
116
|
-
</div>
|
|
117
|
-
)}
|
|
118
|
-
</button>
|
|
119
|
-
);
|
|
120
|
-
}
|
package/components/checkBox.tsx
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
|
|
4
|
-
interface CheckboxProps {
|
|
5
|
-
label?: string;
|
|
6
|
-
checked?: boolean;
|
|
7
|
-
onChange?: (checked: boolean) => void;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export default function Checkbox({
|
|
11
|
-
label,
|
|
12
|
-
checked = false,
|
|
13
|
-
onChange
|
|
14
|
-
}: CheckboxProps) {
|
|
15
|
-
const [isChecked, setIsChecked] = useState(checked);
|
|
16
|
-
|
|
17
|
-
const handleToggle = () => {
|
|
18
|
-
const newChecked = !isChecked;
|
|
19
|
-
setIsChecked(newChecked);
|
|
20
|
-
if (onChange) {
|
|
21
|
-
onChange(newChecked);
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<label className="flex items-center cursor-pointer">
|
|
27
|
-
<div
|
|
28
|
-
onClick={handleToggle}
|
|
29
|
-
className="relative flex items-center justify-center w-6 h-6 group transition-colors duration-200 ease-in-out"
|
|
30
|
-
>
|
|
31
|
-
{/* Circle behind the checkbox */}
|
|
32
|
-
<div
|
|
33
|
-
className="absolute w-6 h-6 rounded-full group-hover:bg-light-action-selected dark:group-hover:bg-dark-action-selected"
|
|
34
|
-
></div>
|
|
35
|
-
|
|
36
|
-
{/* Checkbox */}
|
|
37
|
-
<div
|
|
38
|
-
className={`relative z-10 w-4 h-4 border-2 rounded ${
|
|
39
|
-
isChecked
|
|
40
|
-
? 'bg-light-text-primary dark:bg-dark-text-primary border-none'
|
|
41
|
-
: 'border-light-text-secondary dark:border-dark-text-secondary'
|
|
42
|
-
} flex items-center justify-center`}
|
|
43
|
-
>
|
|
44
|
-
{isChecked && (
|
|
45
|
-
<svg
|
|
46
|
-
className="w-3 h-3 text-light-text-contrast dark:text-dark-text-contrast"
|
|
47
|
-
fill="none"
|
|
48
|
-
stroke="currentColor"
|
|
49
|
-
viewBox="0 0 12 12"
|
|
50
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
51
|
-
>
|
|
52
|
-
<path strokeWidth="2" d="M1 6l4 3 6-7" />
|
|
53
|
-
</svg>
|
|
54
|
-
)}
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
{label && <span className="ml-2 text-body1 text-light-text-primary dark:text-dark-text-primary">{label}</span>}
|
|
58
|
-
</label>
|
|
59
|
-
);
|
|
60
|
-
}
|
package/components/emptyBox.tsx
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import React, { useState } from 'react';
|
|
3
|
-
import * as HeroIcons24 from '@heroicons/react/24/outline';
|
|
4
|
-
import * as HeroIcons20 from '@heroicons/react/20/solid';
|
|
5
|
-
import { LoadingSmall } from './loadingScreen';
|
|
6
|
-
|
|
7
|
-
interface IconButtonProps {
|
|
8
|
-
color: "primary" | "secondary" | "tertiary" | "iconOnly";
|
|
9
|
-
state: "enabled" | "selected" | "disabled";
|
|
10
|
-
size: "large" | "medium" | "small";
|
|
11
|
-
iconName: keyof typeof HeroIcons24 & keyof typeof HeroIcons20;
|
|
12
|
-
onClick?: any;
|
|
13
|
-
loading?: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
type IconType = React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
|
17
|
-
|
|
18
|
-
export default function IconButton({
|
|
19
|
-
color,
|
|
20
|
-
state,
|
|
21
|
-
size,
|
|
22
|
-
iconName,
|
|
23
|
-
onClick,
|
|
24
|
-
loading = false, // Default to false
|
|
25
|
-
}: IconButtonProps) {
|
|
26
|
-
const [isHovered, setIsHovered] = useState(false);
|
|
27
|
-
|
|
28
|
-
// Select icon based on size
|
|
29
|
-
const Icon: IconType = size === 'small'
|
|
30
|
-
? HeroIcons20[iconName]
|
|
31
|
-
: HeroIcons24[iconName];
|
|
32
|
-
|
|
33
|
-
// Size-based classes
|
|
34
|
-
const sizeClasses = {
|
|
35
|
-
large: 'p-2', // 8px padding
|
|
36
|
-
medium: 'p-1', // 4px padding
|
|
37
|
-
small: 'p-1', // 4px padding
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const iconSizeClasses = {
|
|
41
|
-
large: 'size-6', // 24px
|
|
42
|
-
medium: 'size-6', // 24px
|
|
43
|
-
small: 'size-5', // 20px
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// Base classes (padding and corner radius)
|
|
47
|
-
const baseClasses = `${sizeClasses[size]} rounded-[8px] leading-none relative`;
|
|
48
|
-
|
|
49
|
-
// Background color
|
|
50
|
-
const bgColor = color === 'primary'
|
|
51
|
-
? 'bg-light-accent-main dark:bg-dark-accent-main' // Primary
|
|
52
|
-
: color === 'secondary'
|
|
53
|
-
? 'bg-light-secondary-main dark:bg-dark-secondary-main' // Secondary
|
|
54
|
-
: color === 'tertiary'
|
|
55
|
-
? 'bg-light-background-accent100 dark:bg-dark-background-accent100' // Tertiary
|
|
56
|
-
: ' '; // iconOnly: none
|
|
57
|
-
|
|
58
|
-
// Background hover color
|
|
59
|
-
const bgColorHover = color === 'primary'
|
|
60
|
-
? 'hover:bg-light-accent-dark hover:dark:bg-dark-accent-dark' // Primary
|
|
61
|
-
: color === 'secondary'
|
|
62
|
-
? 'hover:bg-light-secondary-dark hover:dark:bg-dark-secondary-dark' // Secondary
|
|
63
|
-
: color === 'tertiary'
|
|
64
|
-
? 'hover:bg-light-background-accent200 hover:dark:bg-dark-background-accent200' // Tertiary
|
|
65
|
-
: 'hover:bg-light-background-accent200 hover:dark:bg-dark-background-accent200'; // iconOnly
|
|
66
|
-
|
|
67
|
-
// Icon color
|
|
68
|
-
const iconColor = color === 'primary'
|
|
69
|
-
? 'text-light-text-primary dark:text-dark-text-contrast' // Primary
|
|
70
|
-
: color === 'secondary'
|
|
71
|
-
? 'text-light-text-primary dark:text-dark-text-primary' // Secondary
|
|
72
|
-
: color === 'tertiary'
|
|
73
|
-
? 'text-light-text-primary dark:text-dark-text-primary' // Tertiary
|
|
74
|
-
: 'text-light-text-primary dark:text-dark-text-primary'; // iconOnly
|
|
75
|
-
|
|
76
|
-
// State classes, including loading
|
|
77
|
-
const stateClasses = loading
|
|
78
|
-
? 'cursor-wait' // Show a waiting cursor during loading
|
|
79
|
-
: state === 'disabled'
|
|
80
|
-
? 'cursor-not-allowed opacity-50'
|
|
81
|
-
: state === 'selected'
|
|
82
|
-
? 'cursor-pointer ring-2 ring-offset-2 ring-blue-500'
|
|
83
|
-
: isHovered
|
|
84
|
-
? 'cursor-pointer hover:bg-opacity-75'
|
|
85
|
-
: 'cursor-pointer';
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
<button
|
|
89
|
-
className={`${baseClasses} ${bgColor} ${iconColor} ${bgColorHover} ${stateClasses} transition-colors duration-200 ease-in-out flex items-center justify-center`}
|
|
90
|
-
disabled={state === 'disabled' || loading} // Disable button during loading
|
|
91
|
-
onMouseEnter={() => setIsHovered(true)}
|
|
92
|
-
onMouseLeave={() => setIsHovered(false)}
|
|
93
|
-
onClick={onClick}
|
|
94
|
-
aria-label={loading ? 'Loading' : 'Reload'}
|
|
95
|
-
>
|
|
96
|
-
{loading ? (
|
|
97
|
-
<LoadingSmall size={size} /> // Pass the size prop to match the icon
|
|
98
|
-
) : (
|
|
99
|
-
Icon && <Icon className={iconSizeClasses[size]} /> // Show icon when not loading
|
|
100
|
-
)}
|
|
101
|
-
</button>
|
|
102
|
-
);
|
|
103
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
|
|
4
|
-
interface LoadingSmallProps {
|
|
5
|
-
size?: 'large' | 'medium' | 'small'; // Match IconButton sizes
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export default function LoadingScreen() {
|
|
9
|
-
return (
|
|
10
|
-
<div className="flex justify-center items-center h-full w-full min-h-[200px]">
|
|
11
|
-
<div className="w-12 h-12 border-4 border-t-transparent border-light-accent-main rounded-full animate-spin"></div>
|
|
12
|
-
</div>
|
|
13
|
-
);
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export function LoadingSmall({ size = 'medium' }: LoadingSmallProps) {
|
|
17
|
-
const spinnerSizeClasses = {
|
|
18
|
-
large: 'w-6 h-6 border-2', // 24px, border-2 for proportional thickness
|
|
19
|
-
medium: 'w-6 h-6 border-2', // 24px, border-2
|
|
20
|
-
small: 'w-5 h-5 border-2', // 20px, slightly thinner border
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
return (
|
|
24
|
-
<div className="flex justify-center items-center">
|
|
25
|
-
<div
|
|
26
|
-
className={`border-t-transparent border-light-accent-main rounded-full animate-spin ${spinnerSizeClasses[size]}`}
|
|
27
|
-
/>
|
|
28
|
-
</div>
|
|
29
|
-
);
|
|
30
|
-
}
|
package/components/menu.tsx
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import React, { forwardRef } from 'react';
|
|
3
|
-
|
|
4
|
-
interface MenuProps {
|
|
5
|
-
children: React.ReactNode;
|
|
6
|
-
width?: number | string;
|
|
7
|
-
className?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const Menu = forwardRef<HTMLDivElement, MenuProps>(
|
|
11
|
-
({ children, width, className = '' }, ref) => {
|
|
12
|
-
return (
|
|
13
|
-
<div
|
|
14
|
-
ref={ref}
|
|
15
|
-
className={`
|
|
16
|
-
bg-light-background-accent300 dark:bg-dark-background-accent300
|
|
17
|
-
rounded-[8px]
|
|
18
|
-
shadow-lg
|
|
19
|
-
overflow-hidden
|
|
20
|
-
${className}
|
|
21
|
-
`}
|
|
22
|
-
style={{ width: width || 'auto' }}
|
|
23
|
-
>
|
|
24
|
-
<div className="p-[10px]">
|
|
25
|
-
{children}
|
|
26
|
-
</div>
|
|
27
|
-
</div>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
Menu.displayName = 'Menu';
|
|
33
|
-
|
|
34
|
-
export default Menu;
|
|
35
|
-
export { Menu };
|
package/components/menuItem.tsx
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import React, { useState, useEffect, useCallback, SVGProps, JSX } from 'react';
|
|
3
|
-
import NextLink from 'next/link';
|
|
4
|
-
import UserImage from './userImage';
|
|
5
|
-
|
|
6
|
-
type IconType = (props: SVGProps<SVGSVGElement>) => JSX.Element;
|
|
7
|
-
|
|
8
|
-
interface MenuItemProps {
|
|
9
|
-
href?: string;
|
|
10
|
-
iconName?: string;
|
|
11
|
-
userHandle?: string;
|
|
12
|
-
userImgUrl?: string;
|
|
13
|
-
label: string;
|
|
14
|
-
isSelected?: boolean;
|
|
15
|
-
onClick?: any;
|
|
16
|
-
className?: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export default function MenuItem({
|
|
20
|
-
href,
|
|
21
|
-
iconName,
|
|
22
|
-
userHandle,
|
|
23
|
-
userImgUrl,
|
|
24
|
-
label,
|
|
25
|
-
isSelected,
|
|
26
|
-
onClick,
|
|
27
|
-
className = '',
|
|
28
|
-
}: MenuItemProps) {
|
|
29
|
-
const [IconLeft, setIconLeft] = useState<IconType | null>(null);
|
|
30
|
-
|
|
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 IconComponent = module[iconName as keyof typeof module] as IconType;
|
|
36
|
-
return IconComponent || null;
|
|
37
|
-
} catch (error) {
|
|
38
|
-
console.error(`Failed to load icon ${iconName}:`, error);
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
}, []);
|
|
42
|
-
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
const fetchIcon = async () => {
|
|
45
|
-
if (iconName) {
|
|
46
|
-
setIconLeft(await loadIcon(iconName));
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
fetchIcon();
|
|
50
|
-
}, [iconName, loadIcon]);
|
|
51
|
-
|
|
52
|
-
const content = (
|
|
53
|
-
<div
|
|
54
|
-
className={`
|
|
55
|
-
flex items-center space-x-2 p-2 rounded-[8px] cursor-pointer justify-start transition-colors duration-200 ease-in-out
|
|
56
|
-
${isSelected
|
|
57
|
-
? 'bg-light-background-accent300 dark:bg-dark-background-accent300 hover:bg-light-background-accent200 dark:hover:bg-dark-background-accent200'
|
|
58
|
-
: 'hover:bg-light-background-accent200 hover:dark:bg-dark-background-accent200'}
|
|
59
|
-
${className}
|
|
60
|
-
`}
|
|
61
|
-
style={{ width: '100%' }}
|
|
62
|
-
onClick={onClick}
|
|
63
|
-
>
|
|
64
|
-
{userImgUrl ? (
|
|
65
|
-
<UserImage userHandle={userHandle || ''} userImgUrl={userImgUrl} />
|
|
66
|
-
) : (
|
|
67
|
-
IconLeft && (
|
|
68
|
-
<IconLeft
|
|
69
|
-
className="w-6 h-6 text-light-text-secondary dark:text-dark-text-secondary"
|
|
70
|
-
/>
|
|
71
|
-
)
|
|
72
|
-
)}
|
|
73
|
-
<span className="whitespace-nowrap text-body1 px-2 text-light-text-primary dark:text-dark-text-primary">
|
|
74
|
-
{label}
|
|
75
|
-
</span>
|
|
76
|
-
</div>
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
return href ? <NextLink href={href}>{content}</NextLink> : content;
|
|
80
|
-
}
|
package/components/modal.tsx
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import React, { useEffect, useRef } from 'react';
|
|
3
|
-
|
|
4
|
-
interface ModalProps {
|
|
5
|
-
isOpen: boolean;
|
|
6
|
-
title?: string;
|
|
7
|
-
children: React.ReactNode;
|
|
8
|
-
onClose: () => void;
|
|
9
|
-
actions?: React.ReactNode;
|
|
10
|
-
size?: 'medium' | 'large';
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export default function Modal({
|
|
14
|
-
isOpen,
|
|
15
|
-
title,
|
|
16
|
-
children,
|
|
17
|
-
onClose,
|
|
18
|
-
actions,
|
|
19
|
-
size = 'medium', // Default size is medium
|
|
20
|
-
}: ModalProps) {
|
|
21
|
-
|
|
22
|
-
if (!isOpen) return null;
|
|
23
|
-
|
|
24
|
-
// close modal by clicking elsewhere
|
|
25
|
-
const handleOverlayClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
26
|
-
if (e.target === e.currentTarget) {
|
|
27
|
-
onClose();
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
// close modal by esc keypress
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
const handleKeyDown = (e: KeyboardEvent) => {
|
|
34
|
-
if (e.key === 'Escape') {
|
|
35
|
-
onClose();
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
window.addEventListener('keydown', handleKeyDown);
|
|
39
|
-
return () => {
|
|
40
|
-
window.removeEventListener('keydown', handleKeyDown);
|
|
41
|
-
};
|
|
42
|
-
}, [onClose]);
|
|
43
|
-
|
|
44
|
-
// Determine width based on size prop
|
|
45
|
-
const modalWidth = size === 'large' ? 'w-[1200px]' : 'w-[600px]';
|
|
46
|
-
const maxWidth = size === 'large' ? '1200px' : '600px';
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<div
|
|
52
|
-
className="fixed inset-[-32px] bg-black bg-opacity-50 flex items-center justify-center z-50"
|
|
53
|
-
onClick={handleOverlayClick}
|
|
54
|
-
role="dialog"
|
|
55
|
-
aria-labelledby="modal-title"
|
|
56
|
-
aria-modal="true"
|
|
57
|
-
tabIndex={-1}
|
|
58
|
-
>
|
|
59
|
-
<div
|
|
60
|
-
className={`bg-light-background-default dark:bg-dark-background-default p-6 rounded-[8px] space-y-4 ${modalWidth}`}
|
|
61
|
-
style={{
|
|
62
|
-
maxWidth,
|
|
63
|
-
width: 'calc(100vw - 64px)',
|
|
64
|
-
maxHeight: '800px',
|
|
65
|
-
height: 'auto',
|
|
66
|
-
overflowY: 'auto',
|
|
67
|
-
}}
|
|
68
|
-
>
|
|
69
|
-
{title && (
|
|
70
|
-
<h2 id="modal-title" className="text-h6">
|
|
71
|
-
{title}
|
|
72
|
-
</h2>
|
|
73
|
-
)}
|
|
74
|
-
<div className="text-body1 space-y-4">
|
|
75
|
-
{children}
|
|
76
|
-
</div>
|
|
77
|
-
{actions && (
|
|
78
|
-
<div className="flex justify-between">
|
|
79
|
-
{actions}
|
|
80
|
-
</div>
|
|
81
|
-
)}
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
);
|
|
85
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
|
|
4
|
-
interface NavigationProps {
|
|
5
|
-
children: React.ReactNode;
|
|
6
|
-
className?: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export default function Navigation({
|
|
10
|
-
children,
|
|
11
|
-
className = '',
|
|
12
|
-
}: NavigationProps) {
|
|
13
|
-
return (
|
|
14
|
-
<nav
|
|
15
|
-
className={`
|
|
16
|
-
bg-light-background-default dark:bg-dark-background-default
|
|
17
|
-
border-r border-light-misc-divider dark:border-dark-misc-divider
|
|
18
|
-
p-[10px]
|
|
19
|
-
${className}
|
|
20
|
-
`}
|
|
21
|
-
>
|
|
22
|
-
{children}
|
|
23
|
-
</nav>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export { Navigation };
|