gwan-design-system 0.1.1

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 (91) hide show
  1. package/.github/workflows/bump-version.yml +50 -0
  2. package/.github/workflows/publish.yml +43 -0
  3. package/CODEOWNERS +1 -0
  4. package/README.md +54 -0
  5. package/eslint.config.mjs +16 -0
  6. package/next.config.ts +7 -0
  7. package/package.json +27 -0
  8. package/postcss.config.mjs +7 -0
  9. package/public/file.svg +1 -0
  10. package/public/globe.svg +1 -0
  11. package/public/images/empty.png +0 -0
  12. package/public/images/empty_state.png +0 -0
  13. package/public/images/hero.png +0 -0
  14. package/public/images/hero2.png +0 -0
  15. package/public/images/logo.png +0 -0
  16. package/public/images/logo_short.png +0 -0
  17. package/public/images/profile_picture.png +0 -0
  18. package/public/images/success.png +0 -0
  19. package/public/next.svg +1 -0
  20. package/public/vercel.svg +1 -0
  21. package/public/window.svg +1 -0
  22. package/src/app/components-library/page.tsx +8 -0
  23. package/src/app/favicon.ico +0 -0
  24. package/src/app/globals.css +108 -0
  25. package/src/app/layout.tsx +34 -0
  26. package/src/app/page.tsx +5 -0
  27. package/src/components/avatar/index.tsx +109 -0
  28. package/src/components/banner/index.tsx +58 -0
  29. package/src/components/button/index.tsx +61 -0
  30. package/src/components/carousel/index.tsx +58 -0
  31. package/src/components/checkbox/index.tsx +48 -0
  32. package/src/components/chip/index.tsx +22 -0
  33. package/src/components/ellipsis/index.tsx +36 -0
  34. package/src/components/fileUploader/index.tsx +54 -0
  35. package/src/components/filterDropdown/index.tsx +49 -0
  36. package/src/components/icons/arrowLeftSVG/index.tsx +14 -0
  37. package/src/components/icons/checkSVG/index.tsx +14 -0
  38. package/src/components/icons/chevDownSVG/index.tsx +14 -0
  39. package/src/components/icons/chevLeftSVG/index.tsx +14 -0
  40. package/src/components/icons/chevRightSVG/index.tsx +14 -0
  41. package/src/components/icons/circleSVG/index.tsx +14 -0
  42. package/src/components/icons/colorsSVG/index.tsx +21 -0
  43. package/src/components/icons/coversSVG/index.tsx +21 -0
  44. package/src/components/icons/crossSVG/index.tsx +16 -0
  45. package/src/components/icons/dashboardSVG/index.tsx +14 -0
  46. package/src/components/icons/filterSVG/index.tsx +21 -0
  47. package/src/components/icons/index.tsx +17 -0
  48. package/src/components/icons/orderInfoSVG/index.tsx +21 -0
  49. package/src/components/icons/ordersSVG/index.tsx +21 -0
  50. package/src/components/icons/productsSVG/index.tsx +21 -0
  51. package/src/components/icons/signOutSVG/index.tsx +21 -0
  52. package/src/components/icons/templatesSVG/index.tsx +24 -0
  53. package/src/components/icons/uploadSVG/index.tsx +21 -0
  54. package/src/components/input/index.tsx +40 -0
  55. package/src/components/modal/index.tsx +54 -0
  56. package/src/components/navBar/index.tsx +161 -0
  57. package/src/components/pagination/index.tsx +69 -0
  58. package/src/components/radioButton/index.tsx +44 -0
  59. package/src/components/selectDropdown/index.tsx +90 -0
  60. package/src/components/snackBar/index.tsx +46 -0
  61. package/src/components/state/index.tsx +69 -0
  62. package/src/components/table/index.tsx +51 -0
  63. package/src/components/tag/index.tsx +33 -0
  64. package/src/components/timeLine/index.tsx +99 -0
  65. package/src/components/tooltip/index.tsx +70 -0
  66. package/src/index.ts +22 -0
  67. package/src/templates/component-library/avatars/index.tsx +45 -0
  68. package/src/templates/component-library/banners/index.tsx +35 -0
  69. package/src/templates/component-library/buttons/index.tsx +122 -0
  70. package/src/templates/component-library/carousels/index.tsx +38 -0
  71. package/src/templates/component-library/checkboxes/index.tsx +19 -0
  72. package/src/templates/component-library/chips/index.tsx +49 -0
  73. package/src/templates/component-library/ellipsis/index.tsx +20 -0
  74. package/src/templates/component-library/fileUploaders/index.tsx +21 -0
  75. package/src/templates/component-library/filterDropdown/index.tsx +48 -0
  76. package/src/templates/component-library/icons/index.tsx +23 -0
  77. package/src/templates/component-library/index.tsx +179 -0
  78. package/src/templates/component-library/input/index.tsx +35 -0
  79. package/src/templates/component-library/modals/index.tsx +113 -0
  80. package/src/templates/component-library/navBars/index.tsx +91 -0
  81. package/src/templates/component-library/pagination/index.tsx +28 -0
  82. package/src/templates/component-library/radioButtons/index.tsx +33 -0
  83. package/src/templates/component-library/selectDropdown/index.tsx +90 -0
  84. package/src/templates/component-library/snackBars/index.tsx +34 -0
  85. package/src/templates/component-library/states/index.tsx +24 -0
  86. package/src/templates/component-library/tables/index.tsx +143 -0
  87. package/src/templates/component-library/tags/index.tsx +15 -0
  88. package/src/templates/component-library/timeLines/index.tsx +96 -0
  89. package/src/templates/component-library/tooltips/index.tsx +61 -0
  90. package/tsconfig.build.json +16 -0
  91. package/tsconfig.json +27 -0
@@ -0,0 +1,40 @@
1
+ export interface IInput {
2
+ label: string;
3
+ value: string;
4
+ onChange: (value: string) => void;
5
+ disabled?: boolean;
6
+ placeholder?: string;
7
+ inputClassName?: string;
8
+ required?: boolean;
9
+ }
10
+
11
+ const Input = ({
12
+ label,
13
+ value,
14
+ onChange,
15
+ disabled,
16
+ placeholder,
17
+ inputClassName = "",
18
+ required = false,
19
+ }: IInput) => {
20
+ return (
21
+ <div className="flex flex-col gap-1 relative">
22
+ <label htmlFor={label} className="text-sm text-neutrola-600 mb-2">
23
+ {`${label}${required ? " *" : ""}`}
24
+ </label>
25
+ <input
26
+ id={label}
27
+ placeholder={placeholder}
28
+ value={value}
29
+ onChange={(e) => onChange(e.target.value)}
30
+ disabled={disabled}
31
+ className={`border border-neutrola-300 outline-none p-4 rounded-lg ${
32
+ disabled ? "cursor-not-allowed" : "cursor-text"
33
+ } text-sm w-full ${inputClassName}`}
34
+ required={required}
35
+ />
36
+ </div>
37
+ );
38
+ };
39
+
40
+ export default Input;
@@ -0,0 +1,54 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { CrossSVG } from "../icons";
3
+
4
+ export enum MODAL_SIZE {
5
+ SMALL = "w-[600px] h-60",
6
+ MEDIUM = "w-[800px] h-96",
7
+ LARGE = "w-[950px] h-[600px]",
8
+ FULL = "w-full h-full",
9
+ }
10
+
11
+ interface IModal {
12
+ title: string;
13
+ children: React.ReactNode;
14
+ onClear: () => void;
15
+ size?: MODAL_SIZE;
16
+ }
17
+
18
+ const Modal = ({
19
+ title,
20
+ children,
21
+ onClear,
22
+ size = MODAL_SIZE.MEDIUM,
23
+ }: IModal) => {
24
+ const modalRef = useRef<HTMLDivElement>(null);
25
+
26
+ useEffect(() => {
27
+ modalRef.current?.focus();
28
+ }, []);
29
+
30
+ return (
31
+ <div className="fixed inset-0 flex items-center justify-center bg-[rgba(0,0,0,0.3)]">
32
+ <div
33
+ ref={modalRef}
34
+ tabIndex={-1}
35
+ className={`bg-white p-4 ${
36
+ size !== MODAL_SIZE.FULL && "rounded-lg"
37
+ } absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 flex flex-col gap-4 ${size}`}
38
+ onBlur={() => onClear()}
39
+ >
40
+ <div className="flex flex-row gap-4 items-center">
41
+ <div className="flex-1 text-3xl">{title}</div>
42
+ <div className="size-4 cursor-pointer" onClick={() => onClear()}>
43
+ <CrossSVG />
44
+ </div>
45
+ </div>
46
+ <div className="flex-1 flex justify-center items-center">
47
+ {children}
48
+ </div>
49
+ </div>
50
+ </div>
51
+ );
52
+ };
53
+
54
+ export default Modal;
@@ -0,0 +1,161 @@
1
+ import Image from "next/image";
2
+ import Avatar, { AVATAR_VARIANT } from "../avatar";
3
+ import { ReactNode, useEffect, useState } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { ChevLeftSVG, ChevRightSVG } from "../icons";
6
+ import Tooltip, { TOOLTIP_POSITION } from "../tooltip";
7
+
8
+ export interface IMenuItem {
9
+ title: string;
10
+ icon?: ReactNode;
11
+ route: string;
12
+ isActive: boolean;
13
+ isDivider: boolean;
14
+ }
15
+
16
+ export interface INavBar {
17
+ menuItems: IMenuItem[];
18
+ logoShort: string;
19
+ logoLong: string;
20
+ avatarName: string;
21
+ avatarEmail: string;
22
+ avatarImage: string;
23
+ avatarType: AVATAR_VARIANT;
24
+ menuWidthClass?: string;
25
+ menuHeightClass?: string;
26
+ isCollapsed?: boolean;
27
+ menuBackgroundColor?: string;
28
+ }
29
+
30
+ const NavBar = ({
31
+ menuItems,
32
+ logoShort,
33
+ logoLong,
34
+ avatarName,
35
+ avatarEmail,
36
+ avatarImage,
37
+ avatarType,
38
+ menuWidthClass = "w-[20rem]",
39
+ menuHeightClass = "h-[100vh]",
40
+ isCollapsed = false,
41
+ menuBackgroundColor = "bg-primary-100",
42
+ }: INavBar) => {
43
+ const router = useRouter();
44
+ const [isActiveMenuItem, setIsActiveMenuItem] = useState<string>("");
45
+ const [isMenuCollapsed, setIsMenuCollapsed] = useState<boolean>(isCollapsed);
46
+ const [isMenuItemsCollapsed, setIsMenuITemsCollapsed] =
47
+ useState<boolean>(false);
48
+ const [showTooltip, setShowTooltip] = useState<number | null>(null);
49
+ const activeClass = "rounded-lg bg-white bg-opacity-35";
50
+ const collapsedClass = "w-[6rem]";
51
+
52
+ useEffect(() => {
53
+ if (menuItems.length > 0) {
54
+ const active = menuItems.find((item) => item.isActive === true);
55
+ setIsActiveMenuItem(active?.title ?? menuItems[0].title);
56
+ }
57
+ }, []);
58
+
59
+ useEffect(() => {
60
+ if (!isMenuCollapsed) {
61
+ setTimeout(() => {
62
+ setIsMenuITemsCollapsed(false);
63
+ }, 200);
64
+ } else {
65
+ setIsMenuITemsCollapsed(true);
66
+ }
67
+ }, [isMenuCollapsed]);
68
+
69
+ const handleClick = (menu: IMenuItem) => {
70
+ setIsActiveMenuItem(menu.title);
71
+ router.push(menu.route);
72
+ };
73
+
74
+ return (
75
+ <div
76
+ className={`transition-[width] duration-300 ease-in-out ${
77
+ isMenuCollapsed ? collapsedClass : menuWidthClass
78
+ } ${menuHeightClass}`}
79
+ >
80
+ <div
81
+ className={`w-full h-full flex flex-col gap-4 p-4 ${menuBackgroundColor}`}
82
+ >
83
+ <div className="flex flex-col gap-4">
84
+ <div className="flex flex-row gap-2 items-center">
85
+ <Image src={logoShort} alt="logo_short" width={60} height={60} />
86
+ {!isMenuItemsCollapsed && (
87
+ <Image
88
+ src={logoLong}
89
+ alt="logo_long"
90
+ width={200}
91
+ height={48}
92
+ className="transition-opacity duration-300"
93
+ />
94
+ )}
95
+ </div>
96
+ <div className="relative">
97
+ <Avatar
98
+ name={avatarName}
99
+ email={avatarEmail}
100
+ variant={
101
+ isMenuItemsCollapsed ? AVATAR_VARIANT.IMAGE_ONLY : avatarType
102
+ }
103
+ image={avatarImage}
104
+ />
105
+ <span
106
+ onClick={() => setIsMenuCollapsed(!isMenuCollapsed)}
107
+ className={`cursor-pointer w-8 h-8 rounded-full ${menuBackgroundColor} border border-neutral-300 absolute -right-8 flex items-center justify-center text-black`}
108
+ >
109
+ <div className="size-5">
110
+ {isMenuCollapsed ? <ChevRightSVG /> : <ChevLeftSVG />}
111
+ </div>
112
+ </span>
113
+ </div>
114
+ </div>
115
+ <div className="border-neutral-300 border-b"></div>
116
+ <div className="flex flex-col gap-1">
117
+ {menuItems.map((item, index) => {
118
+ if (!item.isDivider) {
119
+ return (
120
+ <div
121
+ key={`menu_item_${index + 1}`}
122
+ className={`flex flex-row gap-4 items-center p-4 rounded-lg hover:cursor-pointer hover:bg-white hover:bg-opacity-35 hover:rounded-lg ${
123
+ isActiveMenuItem === item.title ? activeClass : ""
124
+ }`}
125
+ onClick={() => handleClick(item)}
126
+ onMouseEnter={() => setShowTooltip(index)}
127
+ onMouseLeave={() => setShowTooltip(null)}
128
+ >
129
+ <div className="w-6 h-6 ml-1 relative">
130
+ {item.icon}
131
+ {isMenuCollapsed && (
132
+ <Tooltip
133
+ position={TOOLTIP_POSITION.RIGHT}
134
+ label={item.title}
135
+ isVisible={showTooltip === index}
136
+ toolTipWidth="w-fit"
137
+ toolTipClass="text-nowrap"
138
+ />
139
+ )}
140
+ </div>
141
+ {!isMenuItemsCollapsed && (
142
+ <span className="text-nowrap">{item.title}</span>
143
+ )}
144
+ </div>
145
+ );
146
+ }
147
+
148
+ return (
149
+ <div
150
+ key={`menu_item_${index + 1}`}
151
+ className="border-neutral-300 border-b my-3"
152
+ ></div>
153
+ );
154
+ })}
155
+ </div>
156
+ </div>
157
+ </div>
158
+ );
159
+ };
160
+
161
+ export default NavBar;
@@ -0,0 +1,69 @@
1
+ import { useState } from "react";
2
+ import Button, { BUTTON_VARIANTS } from "../button";
3
+ import { ChevLeft } from "../icons/chevLeftSVG";
4
+ import { ChevRight } from "../icons/chevRightSVG";
5
+ import SelectDropdown, { ISelectDropdownOption } from "../selectDropdown";
6
+
7
+ export interface IPaging {
8
+ total: number;
9
+ page: number;
10
+ size: number;
11
+ }
12
+
13
+ export interface IPagination extends IPaging {
14
+ options: ISelectDropdownOption[];
15
+ onChange: (paging: IPaging) => void;
16
+ }
17
+
18
+ const Pagination = ({ total, page, size, options, onChange }: IPagination) => {
19
+ const [optionDropdown, setOptionDropdown] = useState<string>(size.toString());
20
+
21
+ const onLeft = () => {
22
+ if (page > 1) {
23
+ onChange({ total, page: page - 1, size });
24
+ }
25
+ };
26
+
27
+ const onRight = () => {
28
+ if (page < Math.ceil(total / size)) {
29
+ onChange({ total, page: page + 1, size });
30
+ }
31
+ };
32
+
33
+ const handlePageSize = (size: string) => {
34
+ setOptionDropdown(size);
35
+ onChange({ total, page: 1, size: parseInt(size) });
36
+ };
37
+
38
+ return (
39
+ <div className="bg-neutrola-50 flex flex-row gap-4 items-center px-4 py-2 rounded-lg">
40
+ <div className="flex-1">
41
+ {page} of {Math.ceil(total / size)} pages
42
+ </div>
43
+ <div className="w-20">
44
+ <SelectDropdown
45
+ options={options}
46
+ value={optionDropdown}
47
+ onChange={(option) => handlePageSize(option)}
48
+ />
49
+ </div>
50
+ <div>items per page</div>
51
+ <div className="flex flex-row gap-4 items-center">
52
+ <Button
53
+ onClick={onLeft}
54
+ icon={<ChevLeft />}
55
+ variant={BUTTON_VARIANTS.TERTIARY}
56
+ disabled={page === 1}
57
+ />
58
+ <Button
59
+ onClick={onRight}
60
+ icon={<ChevRight />}
61
+ variant={BUTTON_VARIANTS.TERTIARY}
62
+ disabled={page === Math.ceil(total / size)}
63
+ />
64
+ </div>
65
+ </div>
66
+ );
67
+ };
68
+
69
+ export default Pagination;
@@ -0,0 +1,44 @@
1
+ export interface IRadioButton {
2
+ label: string;
3
+ value: string;
4
+ selectedValue?: string;
5
+ onChange?: (value: string) => void;
6
+ }
7
+
8
+ const RadioButton = ({
9
+ label,
10
+ value,
11
+ selectedValue,
12
+ onChange,
13
+ }: IRadioButton) => {
14
+ const isChecked = selectedValue === value;
15
+
16
+ return (
17
+ <label className="flex items-center gap-2 cursor-pointer">
18
+ <div
19
+ className={`w-5 h-5 flex items-center justify-center border-2 rounded-full transition-all
20
+ ${
21
+ isChecked
22
+ ? "border-primary-500 bg-primary-500"
23
+ : "border-gray-400 bg-white"
24
+ }
25
+ `}
26
+ onClick={() => onChange && onChange(value)}
27
+ >
28
+ {isChecked && <div className="w-2.5 h-2.5 bg-white rounded-full"></div>}
29
+ </div>
30
+
31
+ <span className="text-gray-700">{label}</span>
32
+
33
+ <input
34
+ type="radio"
35
+ value={value}
36
+ checked={isChecked}
37
+ onChange={() => onChange && onChange(value)}
38
+ className="hidden"
39
+ />
40
+ </label>
41
+ );
42
+ };
43
+
44
+ export default RadioButton;
@@ -0,0 +1,90 @@
1
+ import { useEffect, useState } from "react";
2
+ import { ChevDownSVG } from "../icons";
3
+
4
+ export interface ISelectDropdownOption {
5
+ value?: string;
6
+ label: string;
7
+ }
8
+
9
+ export interface ISelectDropdown {
10
+ options: ISelectDropdownOption[];
11
+ label?: string;
12
+ placeholder?: string;
13
+ disabled?: boolean;
14
+ value: string;
15
+ onChange: (option: string) => void;
16
+ inputClassName?: string;
17
+ }
18
+
19
+ const SelectDropdown = ({
20
+ options,
21
+ label,
22
+ placeholder = "",
23
+ disabled = false,
24
+ value,
25
+ onChange,
26
+ inputClassName = "",
27
+ }: ISelectDropdown) => {
28
+ const [dropdownValue, setDropdownValue] = useState<string>("");
29
+ const [isOptionsVisible, setIsOptionsVisible] = useState<boolean>(false);
30
+
31
+ useEffect(() => {
32
+ const option = options.find((opt) => opt.label === value);
33
+ if (option) {
34
+ setDropdownValue(option.label);
35
+ onChange(option.label);
36
+ } else {
37
+ setDropdownValue("");
38
+ }
39
+ }, []);
40
+
41
+ const handleMouseDown = (val: string) => {
42
+ setDropdownValue(val);
43
+ onChange(val);
44
+ setIsOptionsVisible(false);
45
+ };
46
+
47
+ return (
48
+ <div className="flex flex-col gap-1 relative">
49
+ {label && (
50
+ <label htmlFor={label} className="text-sm text-neutrola-600 mb-2">
51
+ {label}
52
+ </label>
53
+ )}
54
+ <div className="relative">
55
+ <div className="size-5 absolute inset-y-4 right-4 flex items-center text-neutrola-600 pointer-events-none">
56
+ <ChevDownSVG />
57
+ </div>
58
+ <input
59
+ id={label}
60
+ type="text"
61
+ className={`border border-neutrola-300 outline-none p-4 rounded-lg ${
62
+ disabled ? "cursor-not-allowed" : "cursor-pointer"
63
+ } text-sm w-full ${inputClassName}`}
64
+ placeholder={placeholder}
65
+ onClick={() => setIsOptionsVisible(!isOptionsVisible)}
66
+ value={dropdownValue}
67
+ onBlur={() => setIsOptionsVisible(false)}
68
+ readOnly
69
+ disabled={disabled}
70
+ ></input>
71
+ </div>
72
+
73
+ {isOptionsVisible && (
74
+ <div className="border border-neutrola-300 rounded-lg shadow-lg max-h-96 overflow-y-auto absolute min-w-full top-full bg-white z-10">
75
+ {options.map(({ label, value: val }, index) => (
76
+ <div
77
+ key={`${label}_${val}_${index + 1}`}
78
+ className="p-4 cursor-pointer hover:bg-neutrola-50 text-sm"
79
+ onMouseDown={() => handleMouseDown(label)}
80
+ >
81
+ {label}
82
+ </div>
83
+ ))}
84
+ </div>
85
+ )}
86
+ </div>
87
+ );
88
+ };
89
+
90
+ export default SelectDropdown;
@@ -0,0 +1,46 @@
1
+ export enum SNACK_BAR_TYPE {
2
+ DEFAULT = "default",
3
+ SUCCESS = "success",
4
+ DANGER = "danger",
5
+ WARNING = "warning",
6
+ INFO = "info",
7
+ }
8
+
9
+ export interface ISnackBar {
10
+ type?: SNACK_BAR_TYPE;
11
+ message: string;
12
+ icon?: React.ReactNode;
13
+ }
14
+
15
+ const Snackbar = ({
16
+ type = SNACK_BAR_TYPE.DEFAULT,
17
+ message,
18
+ icon,
19
+ }: ISnackBar) => {
20
+ const getClassName = () => {
21
+ switch (type) {
22
+ case SNACK_BAR_TYPE.SUCCESS:
23
+ return "bg-greenola-50 text-greenola-800 border-greenola-500";
24
+ case SNACK_BAR_TYPE.DANGER:
25
+ return "bg-redola-50 text-redola-600 border-redola-500";
26
+ case SNACK_BAR_TYPE.WARNING:
27
+ return "bg-yellowla-50 text-yellowla-800 border-yellowla-600";
28
+ case SNACK_BAR_TYPE.INFO:
29
+ return "bg-blueola-50 text-blueola-800 border-blueola-600";
30
+ case SNACK_BAR_TYPE.DEFAULT:
31
+ default:
32
+ return "bg-neutrola-50 text-neutrola-800 border-neutrola-500";
33
+ }
34
+ };
35
+
36
+ return (
37
+ <div
38
+ className={`${getClassName()} p-4 rounded-lg flex flex-row gap-4 items-center font-normal border`}
39
+ >
40
+ {icon && <div className="size-5">{icon}</div>}
41
+ <div>{message}</div>
42
+ </div>
43
+ );
44
+ };
45
+
46
+ export default Snackbar;
@@ -0,0 +1,69 @@
1
+ import Image from "next/image";
2
+
3
+ export enum STATE_TYPE {
4
+ EMPTY = "EMPTY",
5
+ SUCCESS = "SUCCESS",
6
+ ERROR = "ERROR",
7
+ LOADING = "LOADING",
8
+ }
9
+
10
+ export interface IStateBase {
11
+ title: string;
12
+ subTitle: string;
13
+ imageWidth?: number;
14
+ imageHeight?: number;
15
+ }
16
+
17
+ export type IState =
18
+ | ({ type: STATE_TYPE; stateImage?: string } & IStateBase)
19
+ | ({ type?: undefined; stateImage: string } & IStateBase);
20
+
21
+ const State = ({
22
+ type,
23
+ stateImage,
24
+ title,
25
+ subTitle,
26
+ imageWidth = 526,
27
+ imageHeight = 526,
28
+ }: IState) => {
29
+ const typeImage = () => {
30
+ switch (type) {
31
+ case STATE_TYPE.EMPTY:
32
+ return "/images/empty.png";
33
+ case STATE_TYPE.SUCCESS:
34
+ return "/images/success.png";
35
+ case STATE_TYPE.ERROR:
36
+ return "/images/empty.png";
37
+ case STATE_TYPE.LOADING:
38
+ return "/images/empty.png";
39
+ default:
40
+ return "/images/empty.png";
41
+ }
42
+ };
43
+
44
+ return (
45
+ <div className="w-full h-full flex flex-col gap-8 items-center justify-center">
46
+ {stateImage ? (
47
+ <Image
48
+ src={stateImage}
49
+ alt="state image"
50
+ width={imageWidth}
51
+ height={imageHeight}
52
+ />
53
+ ) : (
54
+ <Image
55
+ src={typeImage()}
56
+ alt="state image"
57
+ width={imageWidth}
58
+ height={imageHeight}
59
+ />
60
+ )}
61
+ <div className="flex flex-col gap-2">
62
+ <p className="text-3xl text-center font-semibold">{title}</p>
63
+ <p className="w-[300px] text-center">{subTitle}</p>
64
+ </div>
65
+ </div>
66
+ );
67
+ };
68
+
69
+ export default State;
@@ -0,0 +1,51 @@
1
+ import { JSX } from "react";
2
+
3
+ export interface ITableColumn {
4
+ header: string;
5
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
+ render: (row?: any) => JSX.Element;
7
+ headerClassName?: string;
8
+ }
9
+
10
+ export interface ITable {
11
+ columns: ITableColumn[];
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ data: any[];
14
+ }
15
+
16
+ const Table = ({ columns, data }: ITable) => {
17
+ return (
18
+ <div className="overflow-hidden rounded-lg border border-neutrola-300">
19
+ <table className="w-full border-collapse">
20
+ <thead>
21
+ <tr className="bg-neutrola-300 border border-neutrola-300">
22
+ {columns.map(({ header, headerClassName }, index) => (
23
+ <th
24
+ key={`column_${index}`}
25
+ className={`text-left px-4 py-4 ${headerClassName}`}
26
+ >
27
+ {header}
28
+ </th>
29
+ ))}
30
+ </tr>
31
+ </thead>
32
+ <tbody>
33
+ {data.map((row, rowIndex) => (
34
+ <tr
35
+ key={`row_${rowIndex}`}
36
+ className="bg-white hover:bg-neutrola-100"
37
+ >
38
+ {columns.map(({ render }, cellIndex) => (
39
+ <td key={`cell_${cellIndex}`} className="text-left px-4 py-4">
40
+ {render(row)}
41
+ </td>
42
+ ))}
43
+ </tr>
44
+ ))}
45
+ </tbody>
46
+ </table>
47
+ </div>
48
+ );
49
+ };
50
+
51
+ export default Table;
@@ -0,0 +1,33 @@
1
+ export enum TAG_TYPE {
2
+ DEFAULT = "default",
3
+ SUCCESS = "success",
4
+ DANGER = "danger",
5
+ WARNING = "warning",
6
+ INFO = "info",
7
+ }
8
+
9
+ export interface ITag {
10
+ type: TAG_TYPE;
11
+ label: string;
12
+ }
13
+
14
+ const Tag = ({ type, label }: ITag) => {
15
+ const getTagStyle = (type: TAG_TYPE) => {
16
+ switch (type) {
17
+ case TAG_TYPE.SUCCESS:
18
+ return "bg-greenola-50 text-greenola-600";
19
+ case TAG_TYPE.DANGER:
20
+ return "bg-redola-50 text-redola-600";
21
+ case TAG_TYPE.WARNING:
22
+ return "bg-yellowla-50 text-yellowla-600";
23
+ case TAG_TYPE.INFO:
24
+ return "bg-blueola-50 text-blueola-600";
25
+ default:
26
+ return "bg-neutrola-50 text-neutrola-600";
27
+ }
28
+ };
29
+
30
+ return <div className={`w-fit px-4 py-2 rounded-lg text-sm ${getTagStyle(type)}`}>{label}</div>;
31
+ };
32
+
33
+ export default Tag;