@skalfa/skalfa-app 1.0.0
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/.env.example +44 -0
- package/README.md +28 -0
- package/app/auth/edit/page.tsx +65 -0
- package/app/auth/login/page.tsx +63 -0
- package/app/auth/me/page.tsx +58 -0
- package/app/auth/register/page.tsx +69 -0
- package/app/auth/verify/page.tsx +53 -0
- package/app/dashboard/layout.tsx +47 -0
- package/app/dashboard/page.tsx +9 -0
- package/app/dashboard/user/page.tsx +77 -0
- package/app/index.ts +14 -0
- package/app/layout.tsx +38 -0
- package/app/page.tsx +13 -0
- package/barrels.json +6 -0
- package/blueprints/starter.blueprint.json +103 -0
- package/components/base.components/accordion/Accordion.component.tsx +82 -0
- package/components/base.components/breadcrumb/Breadcrumb.component.tsx +80 -0
- package/components/base.components/button/Button.component.tsx +91 -0
- package/components/base.components/button/IconButton.component.tsx +88 -0
- package/components/base.components/button/button.decorate.ts +82 -0
- package/components/base.components/card/AlertCard.component.tsx +69 -0
- package/components/base.components/card/Card.component.tsx +25 -0
- package/components/base.components/card/DashboardCard.component.tsx +44 -0
- package/components/base.components/card/GalleryCard.component.tsx +50 -0
- package/components/base.components/card/ProductCard.component.tsx +65 -0
- package/components/base.components/card/ProfileCard.component.tsx +71 -0
- package/components/base.components/carousel/Carousel.component.tsx +113 -0
- package/components/base.components/chip/Chip.component.tsx +39 -0
- package/components/base.components/document/DocumentViewer.component.tsx +164 -0
- package/components/base.components/document/ExportExcel.component.tsx +340 -0
- package/components/base.components/document/ImportExcel.component.tsx +315 -0
- package/components/base.components/document/PrintTable.component.tsx +204 -0
- package/components/base.components/document/RenderPDF.component.tsx +416 -0
- package/components/base.components/index.ts +85 -0
- package/components/base.components/input/Checkbox.component.tsx +109 -0
- package/components/base.components/input/Input.component.tsx +332 -0
- package/components/base.components/input/InputCheckbox.component.tsx +174 -0
- package/components/base.components/input/InputCurrency.component.tsx +163 -0
- package/components/base.components/input/InputDate.component.tsx +352 -0
- package/components/base.components/input/InputDatetime.component.tsx +260 -0
- package/components/base.components/input/InputDocument.component.tsx +352 -0
- package/components/base.components/input/InputImage.component.tsx +533 -0
- package/components/base.components/input/InputMap.component.tsx +318 -0
- package/components/base.components/input/InputNumber.component.tsx +192 -0
- package/components/base.components/input/InputOtp.component.tsx +169 -0
- package/components/base.components/input/InputPassword.component.tsx +236 -0
- package/components/base.components/input/InputRadio.component.tsx +175 -0
- package/components/base.components/input/InputTime.component.tsx +276 -0
- package/components/base.components/input/InputValues.component.tsx +68 -0
- package/components/base.components/input/Radio.component.tsx +102 -0
- package/components/base.components/input/Select.component.tsx +541 -0
- package/components/base.components/modal/BottomSheet.component.tsx +246 -0
- package/components/base.components/modal/FloatingPage.component.tsx +104 -0
- package/components/base.components/modal/Modal.component.tsx +96 -0
- package/components/base.components/modal/ModalConfirm.component.tsx +218 -0
- package/components/base.components/modal/Toast.component.tsx +126 -0
- package/components/base.components/nav/Bottombar.component.tsx +116 -0
- package/components/base.components/nav/Footer.component.tsx +144 -0
- package/components/base.components/nav/Headbar.component.tsx +104 -0
- package/components/base.components/nav/Navbar.component.tsx +100 -0
- package/components/base.components/nav/Sidebar.component.tsx +301 -0
- package/components/base.components/nav/Tabbar.component.tsx +60 -0
- package/components/base.components/nav/Wizard.component.tsx +73 -0
- package/components/base.components/supervision/FormSupervision.component.tsx +434 -0
- package/components/base.components/supervision/TableSupervision.component.tsx +697 -0
- package/components/base.components/table/ControlBar.component.tsx +497 -0
- package/components/base.components/table/FilterComponent.tsx +518 -0
- package/components/base.components/table/Pagination.component.tsx +159 -0
- package/components/base.components/table/Table.component.tsx +469 -0
- package/components/base.components/typography/TypographyArticle.component.tsx +26 -0
- package/components/base.components/typography/TypographyColumn.component.tsx +20 -0
- package/components/base.components/typography/TypographyContent.component.tsx +20 -0
- package/components/base.components/typography/TypographyTips.component.tsx +20 -0
- package/components/base.components/wrap/Draggable.component.tsx +303 -0
- package/components/base.components/wrap/IDBProvider.tsx +12 -0
- package/components/base.components/wrap/Image.component.tsx +10 -0
- package/components/base.components/wrap/OutsideClick.component.tsx +48 -0
- package/components/base.components/wrap/ScrollContainer.component.tsx +104 -0
- package/components/base.components/wrap/ShortcutProvider.tsx +57 -0
- package/components/base.components/wrap/Swipe.component.tsx +93 -0
- package/components/construct.components/example.tsx +1 -0
- package/components/construct.components/index.ts +5 -0
- package/components/index.ts +3 -0
- package/components/structure.components/example.tsx +1 -0
- package/components/structure.components/index.ts +5 -0
- package/contexts/AppProvider.tsx +12 -0
- package/contexts/Auth.context.tsx +64 -0
- package/contexts/Toggle.context.tsx +44 -0
- package/contexts/index.ts +7 -0
- package/eslint.config.mjs +34 -0
- package/langs/index.ts +1 -0
- package/langs/validation.langs.ts +17 -0
- package/next.config.ts +17 -0
- package/package.json +43 -0
- package/postcss.config.mjs +12 -0
- package/public/204.svg +19 -0
- package/public/500.svg +39 -0
- package/public/images/avatar.jpg +0 -0
- package/public/images/example.png +0 -0
- package/schema/idb/app.schema.ts +9 -0
- package/schema/index.ts +5 -0
- package/styles/globals.css +231 -0
- package/styles/tailwind.safelist +69 -0
- package/tailwind.config.ts +10 -0
- package/tsconfig.json +35 -0
- package/utils/commands/barrels.ts +28 -0
- package/utils/commands/blueprint.ts +421 -0
- package/utils/commands/light.ts +21 -0
- package/utils/commands/logger.ts +42 -0
- package/utils/commands/stubs/table-blueprint.stub +13 -0
- package/utils/commands/use-pdf.ts +29 -0
- package/utils/index.ts +3 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"model" : "iam/user",
|
|
4
|
+
"schema" : {
|
|
5
|
+
"name" : "type:string,200 required min:1 max:200 fillable searchable selectable",
|
|
6
|
+
"email" : "type:string,100 unique index required min:5 max:100 fillable searchable selectable",
|
|
7
|
+
"password" : "type:string,200 required min:8 max:50 fillable hidden",
|
|
8
|
+
"image" : "type:string,100 required fillable selectable file",
|
|
9
|
+
"email_verification_at" : "type:timestamp"
|
|
10
|
+
},
|
|
11
|
+
"relations": {
|
|
12
|
+
"roles": "[]:Role expandable fillable",
|
|
13
|
+
"permission": "[1]Permission"
|
|
14
|
+
},
|
|
15
|
+
"seeders" : [
|
|
16
|
+
["Admin", "admin@mail.com", "$2b$10$tPX5QhnM.vUEDmDpht6O4OarVyTh43NTxhkzFrNxfRijJ3uhSHcli", null, "0001-01-01 01:01:01.000+00"],
|
|
17
|
+
["User", "user@mail.com", "$2b$10$tPX5QhnM.vUEDmDpht6O4OarVyTh43NTxhkzFrNxfRijJ3uhSHcli", null, "0001-01-01 01:01:01.000+00"]
|
|
18
|
+
],
|
|
19
|
+
"migrations" : false,
|
|
20
|
+
|
|
21
|
+
"pages": {
|
|
22
|
+
"iam/user": {
|
|
23
|
+
"features": "create import export print",
|
|
24
|
+
"schema": {
|
|
25
|
+
"Nama" : "name column:sortable form:text|required|min:1|max:200|6",
|
|
26
|
+
"Email" : "email column:sortable form:text|required|email|min:5|max:100|6",
|
|
27
|
+
"Password" : "password form:input-password|required,create|min:8|max:50",
|
|
28
|
+
"Foto" : "image form:image|6"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"model" : "iam/role",
|
|
35
|
+
"schema" : {
|
|
36
|
+
"name" : "type:string,25 index required min:1 max:25 fillable searchable selectable"
|
|
37
|
+
},
|
|
38
|
+
"controllers" : ["iam/role"],
|
|
39
|
+
"seeders" : [["Admin"], ["User"]],
|
|
40
|
+
"migrations" : false,
|
|
41
|
+
|
|
42
|
+
"pages": {
|
|
43
|
+
"iam/user": {
|
|
44
|
+
"features": "create",
|
|
45
|
+
"schema": {
|
|
46
|
+
"Nama" : "name column:sortable form:text|required|min:1|max:25"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"model" : "iam/user-role",
|
|
53
|
+
"schema" : {
|
|
54
|
+
"user_id" : "type:bigInteger unsigned index required fillable selectable",
|
|
55
|
+
"role_id" : "type:bigInteger unsigned index required fillable selectable"
|
|
56
|
+
},
|
|
57
|
+
"relations": {
|
|
58
|
+
"role" : "Role",
|
|
59
|
+
"user" : "User"
|
|
60
|
+
},
|
|
61
|
+
"controllers" : false,
|
|
62
|
+
"seeders" : [[1, 1], [2, 2]],
|
|
63
|
+
"migrations" : false,
|
|
64
|
+
"pages": false
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"model" : "iam/role-permission",
|
|
68
|
+
"schema" : {
|
|
69
|
+
"user_id" : "type:bigInteger unsigned index fillable selectable",
|
|
70
|
+
"role_id" : "type:bigInteger unsigned index fillable selectable",
|
|
71
|
+
"permissions" : "type:json nullable fillable selectable"
|
|
72
|
+
},
|
|
73
|
+
"migrations" : false
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"model" : "iam/user-access-token",
|
|
77
|
+
"controllers" : false,
|
|
78
|
+
"schema" : {
|
|
79
|
+
"user_id" : "type:bigInteger unsigned index fillable selectable",
|
|
80
|
+
"agent" : "type:string,100 nullable index fillable selectable",
|
|
81
|
+
"token" : "type:string,200 fillable selectable hidden",
|
|
82
|
+
"permissions" : "type:json nullable fillable selectable",
|
|
83
|
+
"last_used_ip" : "type:string,100 nullable fillable selectable",
|
|
84
|
+
"last_used_at" : "type:timestamp nullable fillable selectable",
|
|
85
|
+
"expired_at" : "type:timestamp nullable fillable selectable"
|
|
86
|
+
},
|
|
87
|
+
"migrations" : false,
|
|
88
|
+
"pages": false
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"model" : "iam/user-mail-token",
|
|
92
|
+
"controllers" : false,
|
|
93
|
+
"schema" : {
|
|
94
|
+
"user_id" : "type:bigInteger unsigned index fillable selectable",
|
|
95
|
+
"type" : "type:string,10 fillable selectable hidden",
|
|
96
|
+
"token" : "type:string,200 fillable selectable hidden",
|
|
97
|
+
"used_at" : "type:timestamp fillable selectable",
|
|
98
|
+
"expired_at" : "type:timestamp fillable selectable"
|
|
99
|
+
},
|
|
100
|
+
"migrations" : false,
|
|
101
|
+
"pages": false
|
|
102
|
+
}
|
|
103
|
+
]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { ReactNode, useEffect, useState } from "react";
|
|
4
|
+
import { faChevronDown, faChevronLeft } from "@fortawesome/free-solid-svg-icons";
|
|
5
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
6
|
+
import { cn, pcn } from "@utils";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
type CT = "container" | "head" | "active" | "base";
|
|
11
|
+
|
|
12
|
+
export interface AccordionProps {
|
|
13
|
+
setActive ?: number | null;
|
|
14
|
+
items : { head: ReactNode; content: ReactNode }[];
|
|
15
|
+
horizontal ?: boolean;
|
|
16
|
+
className ?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
export function AccordionComponent({
|
|
22
|
+
items,
|
|
23
|
+
setActive = null,
|
|
24
|
+
horizontal = false,
|
|
25
|
+
|
|
26
|
+
/** Use custom class with: "container::", "head::", "active::". */
|
|
27
|
+
className = "",
|
|
28
|
+
}: AccordionProps) {
|
|
29
|
+
const [isActive, setIsActive] = useState<number | null>(setActive);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
setIsActive(setActive);
|
|
33
|
+
}, [setActive]);
|
|
34
|
+
|
|
35
|
+
const styles = {
|
|
36
|
+
container: cn(
|
|
37
|
+
"bg-white border rounded-lg flex",
|
|
38
|
+
horizontal ? "flex-row w-min border-r-0" : "flex-col border-b-0",
|
|
39
|
+
pcn<CT>(className, "container"),
|
|
40
|
+
),
|
|
41
|
+
head: cn(
|
|
42
|
+
"flex justify-between items-center gap-4 font-semibold cursor-pointer",
|
|
43
|
+
horizontal ? "flex-col px-2 py-4" : "py-2 px-4",
|
|
44
|
+
pcn<CT>(className, "head"),
|
|
45
|
+
),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className={styles?.container}>
|
|
50
|
+
{items.map(({ head, content }, key) => (
|
|
51
|
+
<div
|
|
52
|
+
key={key}
|
|
53
|
+
className={cn(horizontal ? "border-r flex" : "border-b")}
|
|
54
|
+
>
|
|
55
|
+
<div
|
|
56
|
+
className={styles?.head}
|
|
57
|
+
onClick={() => setIsActive(isActive === key ? null : key)}
|
|
58
|
+
>
|
|
59
|
+
<div>{head}</div>
|
|
60
|
+
<div
|
|
61
|
+
className={cn(
|
|
62
|
+
"w-min transition-transform",
|
|
63
|
+
isActive !== key && "rotate-180",
|
|
64
|
+
)}
|
|
65
|
+
>
|
|
66
|
+
<FontAwesomeIcon icon={horizontal ? faChevronLeft : faChevronDown} />
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<div
|
|
70
|
+
className={cn(
|
|
71
|
+
"transition-all overflow-hidden",
|
|
72
|
+
horizontal ? isActive === key ? "max-w-max pr-4 py-2" : "max-w-0 px-0"
|
|
73
|
+
: isActive === key ? "max-h-max pb-4 px-4" : "max-h-0 pb-0 px-4",
|
|
74
|
+
pcn<CT>(className, "base"),
|
|
75
|
+
isActive === key && pcn<CT>(className, "active"),
|
|
76
|
+
)}
|
|
77
|
+
>{content}</div>
|
|
78
|
+
</div>
|
|
79
|
+
))}
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Fragment, ReactNode } from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { faChevronRight } from "@fortawesome/free-solid-svg-icons";
|
|
6
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
7
|
+
import { cn, pcn } from "@utils";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
type CT = "container" | "active" | "base";
|
|
12
|
+
|
|
13
|
+
export interface BreadcrumbItemProps {
|
|
14
|
+
path : string;
|
|
15
|
+
label : string;
|
|
16
|
+
className ?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export interface BreadcrumbProps {
|
|
20
|
+
items : BreadcrumbItemProps[];
|
|
21
|
+
square ?: boolean;
|
|
22
|
+
separatorContent ?: string | ReactNode;
|
|
23
|
+
|
|
24
|
+
/** Use custom class with: "container::", "active::". */
|
|
25
|
+
className ?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
export function BreadcrumbComponent({
|
|
30
|
+
items,
|
|
31
|
+
className = "",
|
|
32
|
+
square = false,
|
|
33
|
+
separatorContent,
|
|
34
|
+
}: BreadcrumbProps) {
|
|
35
|
+
return (
|
|
36
|
+
<nav
|
|
37
|
+
className={cn(
|
|
38
|
+
"w-full overflow-x-auto overflow-y-hidden whitespace-nowrap",
|
|
39
|
+
pcn<CT>(className, "container"),
|
|
40
|
+
)}
|
|
41
|
+
>
|
|
42
|
+
<ol className="flex">
|
|
43
|
+
{items.map((item, index) => {
|
|
44
|
+
const isActive = index === items.length - 1;
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Fragment key={item.path}>
|
|
48
|
+
<li>
|
|
49
|
+
<Link
|
|
50
|
+
href={item.path}
|
|
51
|
+
className={cn(
|
|
52
|
+
"capitalize",
|
|
53
|
+
pcn<CT>(className, "base"),
|
|
54
|
+
square && "py-2 px-4 rounded-[6px] bg-light-foreground/10",
|
|
55
|
+
isActive && "text-primary",
|
|
56
|
+
isActive && square && "text-primary bg-primary/10",
|
|
57
|
+
isActive && pcn<CT>(className, "active"),
|
|
58
|
+
)}
|
|
59
|
+
>
|
|
60
|
+
{item.label}
|
|
61
|
+
</Link>
|
|
62
|
+
</li>
|
|
63
|
+
|
|
64
|
+
{!isActive && (
|
|
65
|
+
<li className="mx-2">
|
|
66
|
+
{separatorContent || (
|
|
67
|
+
<FontAwesomeIcon
|
|
68
|
+
icon={faChevronRight}
|
|
69
|
+
className="text-light-foreground"
|
|
70
|
+
/>
|
|
71
|
+
)}
|
|
72
|
+
</li>
|
|
73
|
+
)}
|
|
74
|
+
</Fragment>
|
|
75
|
+
);
|
|
76
|
+
})}
|
|
77
|
+
</ol>
|
|
78
|
+
</nav>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
6
|
+
import { cn, pcn } from "@utils";
|
|
7
|
+
import { buttonVariant, buttonContainer, buttonRadius } from "./button.decorate";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
type CT = "icon" | "loading" | "base";
|
|
12
|
+
|
|
13
|
+
export interface ButtonProps {
|
|
14
|
+
type ?: "submit" | "button";
|
|
15
|
+
label ?: string | ReactNode;
|
|
16
|
+
variant ?: "solid" | "outline" | "light" | "simple";
|
|
17
|
+
paint ?: "primary" | "secondary" | "success" | "danger" | "warning";
|
|
18
|
+
rounded ?: boolean | string;
|
|
19
|
+
block ?: boolean;
|
|
20
|
+
disabled ?: boolean;
|
|
21
|
+
size ?: "xs" | "sm" | "md" | "lg";
|
|
22
|
+
onClick ?: any;
|
|
23
|
+
href ?: string;
|
|
24
|
+
icon ?: any;
|
|
25
|
+
loading ?: boolean;
|
|
26
|
+
hover ?: boolean;
|
|
27
|
+
|
|
28
|
+
/** Use custom class with: "icon::", "loading::". */
|
|
29
|
+
className?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
export function ButtonComponent({
|
|
36
|
+
type = "button",
|
|
37
|
+
label,
|
|
38
|
+
variant = "solid",
|
|
39
|
+
paint = "primary",
|
|
40
|
+
rounded,
|
|
41
|
+
block,
|
|
42
|
+
disabled,
|
|
43
|
+
size = "md",
|
|
44
|
+
onClick,
|
|
45
|
+
href,
|
|
46
|
+
icon,
|
|
47
|
+
loading,
|
|
48
|
+
className = "",
|
|
49
|
+
}: ButtonProps) {
|
|
50
|
+
const buttonClasses = cn(
|
|
51
|
+
"button",
|
|
52
|
+
buttonVariant[variant][paint],
|
|
53
|
+
buttonContainer[size],
|
|
54
|
+
rounded ? "rounded-full" : buttonRadius[size],
|
|
55
|
+
block && "w-full justify-center",
|
|
56
|
+
pcn<CT>(className, "base"),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const loadingClasses = cn(
|
|
60
|
+
"button-loading",
|
|
61
|
+
size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4",
|
|
62
|
+
pcn<CT>(className, "loading"),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const iconClasses = cn(
|
|
66
|
+
size === "xs"? "text-xs" : size === "sm" ? "text-sm mb-0.5" : size === "lg" ? "text-xl mb-0.5" : "text-base mb-0.5",
|
|
67
|
+
pcn<CT>(className, "icon"),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<ButtonComponentWrapper
|
|
72
|
+
type={type}
|
|
73
|
+
className={buttonClasses}
|
|
74
|
+
disabled={disabled || loading}
|
|
75
|
+
onClick={onClick}
|
|
76
|
+
href={href}
|
|
77
|
+
>
|
|
78
|
+
{loading ? (
|
|
79
|
+
<div className={loadingClasses}></div>
|
|
80
|
+
) : (
|
|
81
|
+
icon && <FontAwesomeIcon icon={icon} className={iconClasses} />
|
|
82
|
+
)}
|
|
83
|
+
{label}
|
|
84
|
+
</ButtonComponentWrapper>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
const ButtonComponentWrapper = (props: ButtonProps & {children?: ReactNode}) => {
|
|
90
|
+
return !props.href ? <button {...props} >{props.children}</button> : <Link href={props.href || ""} {...props}></Link>
|
|
91
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
4
|
+
import { cn, pcn } from "@utils";
|
|
5
|
+
import { buttonVariant, buttonRadius, iconButtonContainer } from "./button.decorate";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
type CT = "icon" | "loading" | "base";
|
|
10
|
+
|
|
11
|
+
export interface IconButtonProps {
|
|
12
|
+
icon ?: any;
|
|
13
|
+
type ?: "submit" | "button";
|
|
14
|
+
variant ?: "solid" | "outline" | "light" | "simple";
|
|
15
|
+
rounded ?: boolean | string;
|
|
16
|
+
disabled ?: boolean;
|
|
17
|
+
size ?: "xs" | "sm" | "md" | "lg";
|
|
18
|
+
onClick ?: any;
|
|
19
|
+
loading ?: boolean;
|
|
20
|
+
hover ?: boolean;
|
|
21
|
+
tips ?: string | undefined;
|
|
22
|
+
paint ?: "primary" | "secondary" | "success" | "danger" | "warning";
|
|
23
|
+
customPaint ?: {
|
|
24
|
+
bg ?: string;
|
|
25
|
+
color ?: string;
|
|
26
|
+
border ?: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Use custom class with: "icon::", "loading::". */
|
|
30
|
+
className?: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
export function IconButtonComponent({
|
|
36
|
+
type = "button",
|
|
37
|
+
variant = "solid",
|
|
38
|
+
paint = "primary",
|
|
39
|
+
size = "md",
|
|
40
|
+
rounded,
|
|
41
|
+
disabled,
|
|
42
|
+
onClick,
|
|
43
|
+
icon,
|
|
44
|
+
loading,
|
|
45
|
+
tips,
|
|
46
|
+
className = "",
|
|
47
|
+
}: IconButtonProps) {
|
|
48
|
+
const buttonClasses = cn(
|
|
49
|
+
"button",
|
|
50
|
+
buttonVariant[variant][paint],
|
|
51
|
+
iconButtonContainer[size],
|
|
52
|
+
rounded ? "rounded-full" : buttonRadius[size],
|
|
53
|
+
pcn<CT>(className, "base")
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const loadingClasses = cn(
|
|
57
|
+
"button-loading",
|
|
58
|
+
size === "sm" ? "w-3 h-3" : size === "lg" ? "w-5 h-5" : "w-4 h-4",
|
|
59
|
+
pcn<CT>(className, "loading")
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const iconClasses = cn(
|
|
63
|
+
size === "xs"
|
|
64
|
+
? "text-xs"
|
|
65
|
+
: size === "sm"
|
|
66
|
+
? "text-sm mb-0.5"
|
|
67
|
+
: size === "lg"
|
|
68
|
+
? "text-xl mb-0.5"
|
|
69
|
+
: "text-base mb-0.5",
|
|
70
|
+
pcn<CT>(className, "icon")
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<button
|
|
75
|
+
type={type}
|
|
76
|
+
className={buttonClasses}
|
|
77
|
+
disabled={disabled || loading}
|
|
78
|
+
onClick={onClick}
|
|
79
|
+
title={tips}
|
|
80
|
+
>
|
|
81
|
+
{loading ? (
|
|
82
|
+
<div className={loadingClasses}></div>
|
|
83
|
+
) : (
|
|
84
|
+
<FontAwesomeIcon icon={icon} className={iconClasses} />
|
|
85
|
+
)}
|
|
86
|
+
</button>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export const buttonContainer = {
|
|
2
|
+
lg: "px-14 py-3 flex items-center gap-3",
|
|
3
|
+
md: "px-8 py-2 flex items-center gap-2",
|
|
4
|
+
sm: "px-6 py-2 text-xs flex items-center gap-2",
|
|
5
|
+
xs: "px-3 py-1 text-xs flex items-center gap-1.5",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const iconButtonContainer = {
|
|
9
|
+
lg: "w-10 h-10 flex items-center justify-center",
|
|
10
|
+
md: "w-8 h-8 flex items-center justify-center",
|
|
11
|
+
sm: "w-7 h-7 text-xs flex items-center justify-center",
|
|
12
|
+
xs: "w-6 h-6 text-xs flex items-center justify-center",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const buttonVariant = {
|
|
16
|
+
solid: {
|
|
17
|
+
primary: "bg-primary text-white",
|
|
18
|
+
secondary: "bg-secondary text-white",
|
|
19
|
+
warning: "bg-warning text-white",
|
|
20
|
+
danger: "bg-danger text-white",
|
|
21
|
+
success: "bg-success text-white",
|
|
22
|
+
},
|
|
23
|
+
outline: {
|
|
24
|
+
primary: "border text-primary",
|
|
25
|
+
secondary: "border text-secondary",
|
|
26
|
+
warning: "border text-warning",
|
|
27
|
+
danger: "border text-danger",
|
|
28
|
+
success: "border text-success",
|
|
29
|
+
},
|
|
30
|
+
light: {
|
|
31
|
+
primary: "bg-light-primary text-primary",
|
|
32
|
+
secondary: "bg-light-secondary text-secondary",
|
|
33
|
+
warning: "bg-light-warning text-warning",
|
|
34
|
+
danger: "bg-light-danger text-danger",
|
|
35
|
+
success: "bg-light-success text-success",
|
|
36
|
+
},
|
|
37
|
+
simple: {
|
|
38
|
+
primary: "text-primary",
|
|
39
|
+
secondary: "text-secondary",
|
|
40
|
+
warning: "text-warning",
|
|
41
|
+
danger: "text-danger",
|
|
42
|
+
success: "text-success",
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const buttonIcon = {
|
|
47
|
+
solid: {
|
|
48
|
+
primary: "text-white",
|
|
49
|
+
secondary: "text-white",
|
|
50
|
+
warning: "text-white",
|
|
51
|
+
danger: "text-white",
|
|
52
|
+
success: "text-white",
|
|
53
|
+
},
|
|
54
|
+
outline: {
|
|
55
|
+
primary: "text-primary",
|
|
56
|
+
secondary: "text-secondary",
|
|
57
|
+
warning: "text-warning",
|
|
58
|
+
danger: "text-danger",
|
|
59
|
+
success: "text-success",
|
|
60
|
+
},
|
|
61
|
+
light: {
|
|
62
|
+
primary: "text-primary",
|
|
63
|
+
secondary: "text-secondary",
|
|
64
|
+
warning: "text-warning",
|
|
65
|
+
danger: "text-danger",
|
|
66
|
+
success: "text-success",
|
|
67
|
+
},
|
|
68
|
+
simple: {
|
|
69
|
+
primary: "text-primary",
|
|
70
|
+
secondary: "text-secondary",
|
|
71
|
+
warning: "text-warning",
|
|
72
|
+
danger: "text-danger",
|
|
73
|
+
success: "text-success",
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const buttonRadius = {
|
|
78
|
+
lg: "rounded-sm",
|
|
79
|
+
md: "rounded-sm",
|
|
80
|
+
sm: "rounded-sm",
|
|
81
|
+
xs: "rounded-xs",
|
|
82
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
import { cn, pcn } from "@utils";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
type CT = "badge" | "title" | "base";
|
|
9
|
+
|
|
10
|
+
export interface AlertCardProps {
|
|
11
|
+
title ?: string | ReactNode;
|
|
12
|
+
leftContent ?: string | ReactNode;
|
|
13
|
+
badge ?: string | ReactNode;
|
|
14
|
+
content ?: string | ReactNode;
|
|
15
|
+
footer ?: string | ReactNode;
|
|
16
|
+
|
|
17
|
+
/** Use custom class with: "badge::", "title::". */
|
|
18
|
+
className ?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
export function AlertCardComponent({
|
|
24
|
+
title,
|
|
25
|
+
leftContent,
|
|
26
|
+
badge,
|
|
27
|
+
content,
|
|
28
|
+
footer,
|
|
29
|
+
className = "",
|
|
30
|
+
}: AlertCardProps) {
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<section
|
|
34
|
+
className={cn(
|
|
35
|
+
"rounded-[6px] bg-white p-4 border sm:p-6",
|
|
36
|
+
pcn<CT>(className, "base"),
|
|
37
|
+
)}
|
|
38
|
+
>
|
|
39
|
+
<div className="flex items-start sm:gap-8">
|
|
40
|
+
{leftContent}
|
|
41
|
+
|
|
42
|
+
<div>
|
|
43
|
+
<strong
|
|
44
|
+
className={cn(
|
|
45
|
+
"rounded-[4px] border text-primary bg-primary/20 px-3 py-1.5 text-xs font-bold",
|
|
46
|
+
pcn<CT>(className, "badge"),
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
{badge}
|
|
50
|
+
</strong>
|
|
51
|
+
|
|
52
|
+
<h3
|
|
53
|
+
className={cn(
|
|
54
|
+
"mt-4 text-lg font-medium sm:text-xl",
|
|
55
|
+
pcn<CT>(className, "title"),
|
|
56
|
+
)}
|
|
57
|
+
>
|
|
58
|
+
{title}
|
|
59
|
+
</h3>
|
|
60
|
+
|
|
61
|
+
{content}
|
|
62
|
+
|
|
63
|
+
{footer && <div className="pt-4">{footer}</div>}
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</section>
|
|
67
|
+
</>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@utils";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export interface CardProps {
|
|
8
|
+
children : React.ReactNode;
|
|
9
|
+
className ?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export function CardComponent({
|
|
15
|
+
children,
|
|
16
|
+
className,
|
|
17
|
+
}: CardProps) {
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<div className={cn("px-4 py-2.5 rounded-[6px] border bg-white", className)}>
|
|
21
|
+
{children}
|
|
22
|
+
</div>
|
|
23
|
+
</>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { ReactNode } from "react";
|
|
5
|
+
import { cn } from "@utils";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export interface DashboardCardProps {
|
|
10
|
+
content ?: string | ReactNode;
|
|
11
|
+
title ?: string | ReactNode;
|
|
12
|
+
rightContent ?: string | ReactNode;
|
|
13
|
+
path ?: string;
|
|
14
|
+
className ?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
export function DashboardCardComponent({
|
|
20
|
+
content,
|
|
21
|
+
title,
|
|
22
|
+
rightContent,
|
|
23
|
+
path,
|
|
24
|
+
className,
|
|
25
|
+
} : DashboardCardProps) {
|
|
26
|
+
return (
|
|
27
|
+
<>
|
|
28
|
+
<Link href={path || ""}>
|
|
29
|
+
<div
|
|
30
|
+
className={cn(
|
|
31
|
+
`bg-white border py-4 px-6 rounded-[6px] flex justify-between gap-4 items-center`,
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
>
|
|
35
|
+
<div>
|
|
36
|
+
<div className="flex gap-4 items-center">{content}</div>
|
|
37
|
+
{title}
|
|
38
|
+
</div>
|
|
39
|
+
<div>{rightContent}</div>
|
|
40
|
+
</div>
|
|
41
|
+
</Link>
|
|
42
|
+
</>
|
|
43
|
+
);
|
|
44
|
+
}
|