@skalfa/skalfa-component 1.0.7 → 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.
Files changed (58) hide show
  1. package/package.json +2 -2
  2. package/src/accordion/Accordion.component.tsx +87 -0
  3. package/src/breadcrumb/Breadcrumb.component.tsx +79 -0
  4. package/src/button/Button.component.tsx +89 -0
  5. package/src/card/AlertCard.component.tsx +69 -0
  6. package/src/card/Card.component.tsx +25 -0
  7. package/src/card/DashboardCard.component.tsx +44 -0
  8. package/src/card/GalleryCard.component.tsx +50 -0
  9. package/src/card/ProductCard.component.tsx +65 -0
  10. package/src/card/ProfileCard.component.tsx +71 -0
  11. package/src/carousel/Carousel.component.tsx +111 -0
  12. package/src/chip/Chip.component.tsx +39 -0
  13. package/src/index.ts +70 -0
  14. package/src/input/Checkbox.component.tsx +102 -0
  15. package/src/input/Input.component.tsx +334 -0
  16. package/src/input/InputCheckbox.component.tsx +174 -0
  17. package/src/input/InputCurrency.component.tsx +165 -0
  18. package/src/input/InputDate.component.tsx +356 -0
  19. package/src/input/InputDatetime.component.tsx +267 -0
  20. package/src/input/InputDocument.component.tsx +360 -0
  21. package/src/input/InputImage.component.tsx +535 -0
  22. package/src/input/InputNumber.component.tsx +194 -0
  23. package/src/input/InputOtp.component.tsx +169 -0
  24. package/src/input/InputPassword.component.tsx +245 -0
  25. package/src/input/InputRadio.component.tsx +174 -0
  26. package/src/input/InputTime.component.tsx +280 -0
  27. package/src/input/InputValues.component.tsx +71 -0
  28. package/src/input/Radio.component.tsx +98 -0
  29. package/src/input/Select.component.tsx +557 -0
  30. package/src/modal/BottomSheet.component.tsx +246 -0
  31. package/src/modal/FloatingPage.component.tsx +103 -0
  32. package/src/modal/Modal.component.tsx +95 -0
  33. package/src/modal/ModalConfirm.component.tsx +219 -0
  34. package/src/modal/Toast.component.tsx +125 -0
  35. package/src/nav/Bottombar.component.tsx +72 -0
  36. package/src/nav/Footer.component.tsx +177 -0
  37. package/src/nav/Headbar.component.tsx +33 -0
  38. package/src/nav/Navbar.component.tsx +138 -0
  39. package/src/nav/Sidebar.component.tsx +298 -0
  40. package/src/nav/Tabbar.component.tsx +61 -0
  41. package/src/nav/Wizard.component.tsx +80 -0
  42. package/src/supervision/FormSupervision.component.tsx +425 -0
  43. package/src/supervision/TableSupervision.component.tsx +688 -0
  44. package/src/table/ControlBar.component.tsx +501 -0
  45. package/src/table/FilterComponent.tsx +519 -0
  46. package/src/table/Pagination.component.tsx +152 -0
  47. package/src/table/Table.component.tsx +436 -0
  48. package/src/types.d.ts +7 -0
  49. package/src/typography/TypographyArticle.component.tsx +26 -0
  50. package/src/typography/TypographyColumn.component.tsx +20 -0
  51. package/src/typography/TypographyContent.component.tsx +20 -0
  52. package/src/typography/TypographyTips.component.tsx +20 -0
  53. package/src/wrap/Draggable.component.tsx +303 -0
  54. package/src/wrap/Image.component.tsx +10 -0
  55. package/src/wrap/OutsideClick.component.tsx +48 -0
  56. package/src/wrap/ScrollContainer.component.tsx +107 -0
  57. package/src/wrap/ShortcutProvider.tsx +57 -0
  58. package/src/wrap/Swipe.component.tsx +121 -0
@@ -0,0 +1,125 @@
1
+ "use client"
2
+
3
+ import { ReactNode, useEffect, useState } from "react";
4
+ import { faTimes } from "@fortawesome/free-solid-svg-icons";
5
+ import { cn, pcn } from "@utils";
6
+ import { ButtonComponent } from "../button/Button.component";
7
+
8
+
9
+
10
+ type CT = "base" | "backdrop" | "header" | "footer";
11
+
12
+ export interface ToastProps {
13
+ show: boolean;
14
+ onClose: () => void;
15
+ title?: string | ReactNode;
16
+ children?: any;
17
+ footer?: string | ReactNode;
18
+
19
+ /** Use custom class with: "backdrop::", "header::", "footer::". */
20
+ className?: string;
21
+ };
22
+
23
+
24
+
25
+ export function ToastComponent({
26
+ show,
27
+ onClose,
28
+ title,
29
+ children,
30
+ footer,
31
+ className = "",
32
+ }: ToastProps) {
33
+ const [countdown, setCountdown] = useState(5);
34
+ const [timerId, setTimerId] = useState<NodeJS.Timeout | null>(null);
35
+
36
+ useEffect(() => {
37
+ if (show) {
38
+ setCountdown(5); // Reset countdown saat toast muncul
39
+ const id = setInterval(() => {
40
+ setCountdown((prev) => {
41
+ if (prev <= 1) {
42
+ clearInterval(id);
43
+ onClose();
44
+ return 0;
45
+ }
46
+ return prev - 1;
47
+ });
48
+ }, 1000);
49
+ setTimerId(id);
50
+ }
51
+ return () => {
52
+ if (timerId) {
53
+ clearInterval(timerId);
54
+ }
55
+ };
56
+ }, [show]);
57
+
58
+ const handleMouseEnter = () => {
59
+ if (timerId) {
60
+ clearInterval(timerId);
61
+ setTimerId(null);
62
+ }
63
+ };
64
+
65
+ const handleMouseLeave = () => {
66
+ const id = setInterval(() => {
67
+ setCountdown((prev) => {
68
+ if (prev <= 1) {
69
+ clearInterval(id);
70
+ onClose();
71
+ return 0;
72
+ }
73
+ return prev - 1;
74
+ });
75
+ }, 1000);
76
+ setTimerId(id);
77
+ };
78
+
79
+ return (
80
+ <>
81
+ <div
82
+ className={cn(
83
+ "toast",
84
+ !show && "translate-y-full opacity-0 scale-y-0",
85
+ pcn<CT>(className, "base"),
86
+ )}
87
+ onMouseEnter={handleMouseEnter}
88
+ onMouseLeave={handleMouseLeave}
89
+ >
90
+ {title && (
91
+ <div
92
+ className={cn(
93
+ "pt-2 px-3 flex justify-between items-center text-foreground",
94
+ pcn<CT>(className, "header"),
95
+ )}
96
+ >
97
+ <h6 className="font-semibold">{title}</h6>
98
+
99
+ <div className="flex gap-2 items-center">
100
+ <span className="text-xs text-foreground/50">{countdown}</span>
101
+ <ButtonComponent
102
+ icon={faTimes}
103
+ variant="simple"
104
+ paint="danger"
105
+ onClick={() => {
106
+ clearInterval(timerId || "");
107
+ onClose();
108
+ }}
109
+ size="sm"
110
+ />
111
+ </div>
112
+ </div>
113
+ )}
114
+
115
+ {show && children}
116
+
117
+ {footer && (
118
+ <div className={cn("modal-footer", pcn<CT>(className, "footer"))}>
119
+ {show && footer}
120
+ </div>
121
+ )}
122
+ </div>
123
+ </>
124
+ );
125
+ }
@@ -0,0 +1,72 @@
1
+ "use client"
2
+
3
+ import Link from "next/link";
4
+ import { usePathname } from "next/navigation";
5
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
6
+ import { faClipboard, faCrosshairs, faHome, faSackDollar, faUserCircle } from "@fortawesome/free-solid-svg-icons";
7
+ import { cn, pcn, useKeyboardOpen } from "@utils";
8
+ import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
9
+
10
+ export interface BottombarItemProps {
11
+ icon : IconDefinition;
12
+ path : string;
13
+ activeKey ?: string;
14
+ }
15
+
16
+ export interface BottombarProps {
17
+ active ?: string;
18
+ items ?: BottombarItemProps[];
19
+ className ?: string;
20
+ }
21
+
22
+ const defaultItems: BottombarItemProps[] = [
23
+ { icon: faHome, path: "/_example/bottombar", activeKey: "home" },
24
+ { icon: faClipboard, path: "/", activeKey: "clipboard" },
25
+ { icon: faCrosshairs, path: "/", activeKey: "crosshairs" },
26
+ { icon: faSackDollar, path: "/", activeKey: "sack-dollar" },
27
+ { icon: faUserCircle, path: "/", activeKey: "profile" },
28
+ ];
29
+
30
+ export function BottombarComponent({
31
+ className = "",
32
+ active,
33
+ items = defaultItems,
34
+ }: BottombarProps) {
35
+ const pathname = usePathname();
36
+ const isKeyboardOpen = useKeyboardOpen();
37
+
38
+ const styles = {
39
+ base: cn(
40
+ "bottombar-base",
41
+ isKeyboardOpen ? "-bottom-60" : "bottom-3",
42
+ pcn(className, "base"),
43
+ ),
44
+ item: "bottombar-item",
45
+ };
46
+
47
+ return (
48
+ <>
49
+ <div className={styles.base}>
50
+ <div className="grid grid-cols-5 gap-2 items-center" style={{ gridTemplateColumns: `repeat(${items.length}, minmax(0, 1fr))` }}>
51
+ {items.map((item, key) => {
52
+ const isActive = pathname === item.path || active === item.activeKey;
53
+ return (
54
+ <Link href={item.path} key={key}>
55
+ <div
56
+ className={cn(
57
+ styles.item,
58
+ isActive && "bottombar-item-active",
59
+ pcn(className, "item"),
60
+ isActive && pcn(className, "active"),
61
+ )}
62
+ >
63
+ <FontAwesomeIcon icon={item.icon} />
64
+ </div>
65
+ </Link>
66
+ );
67
+ })}
68
+ </div>
69
+ </div>
70
+ </>
71
+ );
72
+ }
@@ -0,0 +1,177 @@
1
+ "use client"
2
+
3
+ import Link from "next/link";
4
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5
+ import { faFacebook, faGithub, faInstagram, faLinkedin } from "@fortawesome/free-brands-svg-icons";
6
+ import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
7
+
8
+ export interface FooterLinkProps {
9
+ label: string;
10
+ path: string;
11
+ }
12
+
13
+ export interface FooterColumnProps {
14
+ title: string;
15
+ items: FooterLinkProps[];
16
+ }
17
+
18
+ export interface FooterSocialProps {
19
+ icon: IconDefinition;
20
+ path: string;
21
+ }
22
+
23
+ export interface FooterProps {
24
+ brandTitle ?: string;
25
+ brandSubtitle ?: string;
26
+ address ?: { label: string; path: string };
27
+ phone ?: { label: string; path: string };
28
+ email ?: { label: string; path: string };
29
+ supportHours ?: string;
30
+ socials ?: FooterSocialProps[];
31
+ columns ?: FooterColumnProps[];
32
+ copyrightLink ?: { label: string; path: string };
33
+ }
34
+
35
+ const defaultSocials: FooterSocialProps[] = [
36
+ { icon: faFacebook as any, path: "" },
37
+ { icon: faLinkedin as any, path: "" },
38
+ { icon: faGithub as any, path: "https://github.com/SE-JE" },
39
+ { icon: faInstagram as any, path: "https://www.instagram.com/seje.digital/" },
40
+ ];
41
+
42
+ const defaultColumns: FooterColumnProps[] = [
43
+ {
44
+ title: "Link Menu",
45
+ items: [
46
+ { label: "Klik Link Menu 1", path: "" },
47
+ { label: "Klik Link Menu 1", path: "" },
48
+ { label: "Klik Link Menu 1", path: "" },
49
+ { label: "Klik Link Menu 1", path: "" },
50
+ ],
51
+ },
52
+ {
53
+ title: "Link Menu",
54
+ items: [
55
+ { label: "Klik Link Menu 1", path: "" },
56
+ { label: "Klik Link Menu 1", path: "" },
57
+ { label: "Klik Link Menu 1", path: "" },
58
+ { label: "Klik Link Menu 1", path: "" },
59
+ ],
60
+ },
61
+ {
62
+ title: "Link Menu",
63
+ items: [
64
+ { label: "Klik Link Menu 1", path: "" },
65
+ { label: "Klik Link Menu 1", path: "" },
66
+ { label: "Klik Link Menu 1", path: "" },
67
+ { label: "Klik Link Menu 1", path: "" },
68
+ ],
69
+ },
70
+ ];
71
+
72
+ export function FooterComponent({
73
+ brandTitle = "Next Light v.3",
74
+ brandSubtitle = "The Magic Starter Template",
75
+ address = { label: "Soekarno Hatta No 27 C, Ponorogo, Jawa Timur, Indonesia", path: "https://maps.app.goo.gl/TY2QDjFPm3RfwjUq6" },
76
+ phone = { label: "+62 888888888888", path: "https://wa.me/6281456140392" },
77
+ email = { label: "example@gmail.com", path: "mailto:sejedigital@gmail.com" },
78
+ supportHours = "24 / 7 Online Suport | Senin - Sabtu ( 09.00 s/d 17.00 )",
79
+ socials = defaultSocials,
80
+ columns = defaultColumns,
81
+ copyrightLink = { label: "sejedigital.com 2020 - 2025", path: "https://sejedigital.com/" },
82
+ }: FooterProps) {
83
+ return (
84
+ <>
85
+ <div className="footer-base">
86
+ <div className="footer-brand-container">
87
+ <h1 className="footer-brand-title">
88
+ {brandTitle}
89
+ </h1>
90
+ {brandSubtitle && (
91
+ <p className="footer-brand-subtitle">
92
+ {brandSubtitle}
93
+ </p>
94
+ )}
95
+ </div>
96
+
97
+ <div className="footer-contact-container">
98
+ {address && (
99
+ <a
100
+ href={address.path}
101
+ className="footer-contact-link"
102
+ target="_blank"
103
+ rel="noopener noreferrer"
104
+ >
105
+ {address.label}
106
+ </a>
107
+ )}
108
+ {phone && (
109
+ <a
110
+ href={phone.path}
111
+ className="footer-contact-link"
112
+ >
113
+ {phone.label}
114
+ </a>
115
+ )}
116
+ {email && (
117
+ <a
118
+ href={email.path}
119
+ className="footer-contact-link"
120
+ >
121
+ {email.label}
122
+ </a>
123
+ )}
124
+ {supportHours && (
125
+ <p className="footer-support-hours">
126
+ {supportHours}
127
+ </p>
128
+ )}
129
+
130
+ {socials && socials.length > 0 && (
131
+ <div className="footer-socials-container">
132
+ {socials.map((social, key) => (
133
+ <a href={social.path} key={key} target="_blank" rel="noopener noreferrer">
134
+ <FontAwesomeIcon
135
+ icon={social.icon}
136
+ className="footer-social-icon"
137
+ />
138
+ </a>
139
+ ))}
140
+ </div>
141
+ )}
142
+ </div>
143
+
144
+ {columns && columns.length > 0 && (
145
+ <div className="footer-links-container">
146
+ <div className="footer-links-grid">
147
+ {columns.map((column, colKey) => (
148
+ <nav aria-label={`Footer navigation ${column.title}`} key={colKey}>
149
+ <h6 className="footer-menu-title">
150
+ {column.title}
151
+ </h6>
152
+
153
+ <div className="footer-menu-list">
154
+ {column.items.map((item, itemKey) => (
155
+ <Link href={item.path} className="footer-menu-link" key={itemKey}>
156
+ {item.label}
157
+ </Link>
158
+ ))}
159
+ </div>
160
+ </nav>
161
+ ))}
162
+ </div>
163
+ </div>
164
+ )}
165
+
166
+ {copyrightLink && (
167
+ <p className="footer-copyright">
168
+ Copyright &copy;
169
+ <a href={copyrightLink.path} className="ml-1" target="_blank" rel="noopener noreferrer">
170
+ {copyrightLink.label}
171
+ </a>
172
+ </p>
173
+ )}
174
+ </div>
175
+ </>
176
+ );
177
+ }
@@ -0,0 +1,33 @@
1
+ "use client"
2
+
3
+ import { useRouter } from "next/navigation";
4
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5
+ import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
6
+ import { ReactNode } from "react";
7
+
8
+
9
+
10
+ export interface HeadbarProps {
11
+ title ?: string;
12
+ backHref ?: string | boolean;
13
+ rightContent ?: ReactNode;
14
+ };
15
+
16
+
17
+ export function HeadbarComponent({ title, backHref, rightContent }: HeadbarProps) {
18
+ const router = useRouter();
19
+
20
+ return (
21
+ <div className="flex justify-between w-full items-center mt-2 mb-4">
22
+ <div className="flex items-center">
23
+ {backHref && (
24
+ <div className="w-8 aspect-square flex justify-center items-center cursor-pointer" onClick={() => typeof backHref != "boolean" ? router.push(backHref) : router.back()}>
25
+ <FontAwesomeIcon icon={faChevronLeft} className="text-secondary text-xl" />
26
+ </div>
27
+ )}
28
+ <p className="text-lg font-bold px-2">{title}</p>
29
+ </div>
30
+ {rightContent && <div>{rightContent}</div>}
31
+ </div>
32
+ );
33
+ }
@@ -0,0 +1,138 @@
1
+ "use client"
2
+
3
+ import Link from "next/link";
4
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5
+ import { faBell, faHistory, faStore, faUser } from "@fortawesome/free-solid-svg-icons";
6
+ import { ButtonComponent } from "../button/Button.component";
7
+ import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
8
+ import { cn } from "@utils";
9
+
10
+ export interface NavbarItemProps {
11
+ label: string;
12
+ path: string;
13
+ }
14
+
15
+ export interface NavbarProps {
16
+ logoTitle ?: string;
17
+ logoSubtitle ?: string;
18
+ logoPath ?: string;
19
+ specialLink ?: { label: string; path: string; icon?: IconDefinition };
20
+ items ?: NavbarItemProps[];
21
+ isLoggedIn ?: boolean;
22
+ onLogin ?: () => void;
23
+ onRegister ?: () => void;
24
+ onProfile ?: () => void;
25
+ className ?: string;
26
+ }
27
+
28
+ const defaultItems: NavbarItemProps[] = [
29
+ { label: "Tentang", path: "" },
30
+ { label: "Artikel", path: "" },
31
+ { label: "Bantuan", path: "" },
32
+ ];
33
+
34
+ const defaultSpecialLink = {
35
+ label: "Special Link",
36
+ path: "",
37
+ icon: faStore,
38
+ };
39
+
40
+ export function NavbarComponent({
41
+ logoTitle = "Next Light v.3",
42
+ logoSubtitle = "The Magic Starter Template",
43
+ logoPath = "/",
44
+ specialLink = defaultSpecialLink,
45
+ items = defaultItems,
46
+ isLoggedIn = false,
47
+ onLogin,
48
+ onRegister,
49
+ onProfile,
50
+ className = "",
51
+ }: NavbarProps) {
52
+ return (
53
+ <>
54
+ <div className={cn("navbar-topbar", className)}>
55
+ <div className="navbar-container">
56
+ <h2 className="text-base">
57
+ Ini untuk
58
+ <Link href={specialLink.path} className="navbar-special-link">
59
+ {specialLink.icon && <FontAwesomeIcon icon={specialLink.icon} className="mr-1" />}
60
+ {specialLink.label}
61
+ </Link>
62
+ </h2>
63
+
64
+ <div className="navbar-menu">
65
+ {items.map((item, key) => (
66
+ <Link href={item.path} key={key}>
67
+ <div className="navbar-menu-item-sm">{item.label}</div>
68
+ </Link>
69
+ ))}
70
+ </div>
71
+ </div>
72
+ </div>
73
+
74
+ <div className="navbar-main">
75
+ <div className="navbar-main-container">
76
+ <Link href={logoPath}>
77
+ <div className="w-max">
78
+ <h1 className="navbar-logo-title">
79
+ {logoTitle}
80
+ </h1>
81
+ {logoSubtitle && (
82
+ <p className="navbar-logo-subtitle">
83
+ {logoSubtitle}
84
+ </p>
85
+ )}
86
+ </div>
87
+ </Link>
88
+
89
+ <div className="flex gap-12 items-center">
90
+ <div className="navbar-menu">
91
+ {items.map((item, key) => (
92
+ <Link href={item.path} key={key}>
93
+ <div className="navbar-menu-item-base">{item.label}</div>
94
+ </Link>
95
+ ))}
96
+ </div>
97
+
98
+ <div className="w-full flex items-center gap-8">
99
+ {!isLoggedIn ? (
100
+ <div className="navbar-auth-buttons">
101
+ <ButtonComponent
102
+ label={"Masuk"}
103
+ size={"sm"}
104
+ onClick={onLogin}
105
+ />
106
+ <ButtonComponent
107
+ label={"Daftar"}
108
+ size={"sm"}
109
+ variant="light"
110
+ onClick={onRegister}
111
+ />
112
+ </div>
113
+ ) : (
114
+ <div className="flex gap-2 w-max items-center">
115
+ <div className="p-2 text-light-foreground hover:text-foreground cursor-pointer">
116
+ <FontAwesomeIcon icon={faHistory} />
117
+ </div>
118
+ <div className="p-2 text-light-foreground hover:text-foreground cursor-pointer">
119
+ <FontAwesomeIcon icon={faBell} />
120
+ </div>
121
+ <div className="h-5 w-[1px] bg-foreground mx-2.5"></div>
122
+ <div
123
+ className="flex items-center gap-2.5 px-4 cursor-pointer -ml-2 text-light-foreground hover:text-foreground"
124
+ onClick={onProfile}
125
+ >
126
+ <div className="h-10 bg-background rounded-full aspect-square overflow-hidden flex justify-center items-center">
127
+ <FontAwesomeIcon icon={faUser} />
128
+ </div>
129
+ </div>
130
+ </div>
131
+ )}
132
+ </div>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ </>
137
+ );
138
+ }