@kkcompany/app-ui 0.1.30 → 0.1.31

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/index.cjs CHANGED
@@ -712,6 +712,396 @@ const Icon = ({ style = {}, src }) => /* @__PURE__ */ (0, __emotion_react_jsx_ru
712
712
  ] });
713
713
  var Icon_default = Icon;
714
714
 
715
+ //#endregion
716
+ //#region src/Modal/styles.ts
717
+ const modalStyle = __emotion_react.css`
718
+ :where(&) {
719
+ position: fixed;
720
+ top: 0;
721
+ right: 0;
722
+ bottom: 0;
723
+ left: 0;
724
+ z-index: 1000;
725
+ padding: 1rem;
726
+ overflow-x: hidden;
727
+ overflow-y: auto;
728
+ visibility: hidden;
729
+ opacity: 0;
730
+ background-color: rgba(0, 0, 0, 0.7);
731
+ transition: opacity 0.3s;
732
+ }
733
+
734
+ :where(&.modal--show),
735
+ :where(&.modal--shown) {
736
+ display: flex;
737
+ justify-content: center;
738
+ visibility: visible;
739
+ opacity: 1;
740
+ }
741
+
742
+ :where(.modal__content) {
743
+ width: 100%;
744
+ max-width: 700px;
745
+ margin: auto 0;
746
+ }
747
+ `;
748
+
749
+ //#endregion
750
+ //#region src/Modal/index.tsx
751
+ const MODAL_STATE = {
752
+ SHOW: "show",
753
+ SHOWN: "shown",
754
+ HIDE: "hide",
755
+ HIDDEN: "hidden"
756
+ };
757
+ const isShowing = (state) => state === MODAL_STATE.SHOW || state === MODAL_STATE.SHOWN;
758
+ const Modal = (0, react.memo)((0, react.forwardRef)(function Modal$1({ children, className = "", isHideModalOnClickOverlay = true, isShow, onHidden = () => {}, onShown = () => {}, onTriggerHiding = () => {} }, ref) {
759
+ const [visualState, setVisualState] = (0, react.useState)(MODAL_STATE.HIDDEN);
760
+ const shouldLockBody = isShowing(visualState);
761
+ if (isShow && !isShowing(visualState)) setVisualState(MODAL_STATE.SHOW);
762
+ else if (!isShow && isShowing(visualState)) setVisualState(MODAL_STATE.HIDE);
763
+ (0, react.useLayoutEffect)(() => {
764
+ if (!shouldLockBody) return;
765
+ const previousOverflow = document.body.style.overflow;
766
+ document.body.style.overflow = "hidden";
767
+ return () => {
768
+ document.body.style.overflow = previousOverflow;
769
+ };
770
+ }, [shouldLockBody]);
771
+ const previousVisualState = (0, react.useRef)(visualState);
772
+ (0, react.useLayoutEffect)(() => {
773
+ if (previousVisualState.current === visualState) return;
774
+ previousVisualState.current = visualState;
775
+ if (visualState === MODAL_STATE.SHOWN) onShown();
776
+ else if (visualState === MODAL_STATE.HIDDEN) onHidden();
777
+ }, [
778
+ visualState,
779
+ onShown,
780
+ onHidden
781
+ ]);
782
+ const handleAnimationEnd = (0, react.useCallback)(() => {
783
+ setVisualState((previousState) => {
784
+ if (previousState === MODAL_STATE.SHOW) return MODAL_STATE.SHOWN;
785
+ if (previousState === MODAL_STATE.HIDE) return MODAL_STATE.HIDDEN;
786
+ return previousState;
787
+ });
788
+ }, []);
789
+ const handleClick = (0, react.useCallback)((event) => {
790
+ if (event.target.getAttribute("data-dismiss") === "modal") onTriggerHiding();
791
+ }, [onTriggerHiding]);
792
+ const handleKeyDown = (0, react.useCallback)((event) => {
793
+ if (event.key !== "Enter" && event.key !== " ") return;
794
+ if (event.target.getAttribute("data-dismiss") !== "modal") return;
795
+ event.preventDefault();
796
+ onTriggerHiding();
797
+ }, [onTriggerHiding]);
798
+ return /* @__PURE__ */ (0, __emotion_react_jsx_runtime.jsx)("div", {
799
+ css: modalStyle,
800
+ ref,
801
+ role: "presentation",
802
+ className: `modal modal--${visualState} ${className}`,
803
+ onClick: handleClick,
804
+ onKeyDown: handleKeyDown,
805
+ onAnimationEnd: handleAnimationEnd,
806
+ onTransitionEnd: handleAnimationEnd,
807
+ "data-dismiss": isHideModalOnClickOverlay ? "modal" : void 0,
808
+ children: /* @__PURE__ */ (0, __emotion_react_jsx_runtime.jsx)("div", {
809
+ className: "modal__content",
810
+ children
811
+ })
812
+ });
813
+ }));
814
+ var Modal_default = Modal;
815
+
816
+ //#endregion
817
+ //#region src/Pagination/PaginationLink.tsx
818
+ const PaginationLink = ({ routeData, nextLink: NextLinkComponent, children, ...anchorProps }) => {
819
+ const anchor = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
820
+ ...anchorProps,
821
+ href: NextLinkComponent ? void 0 : routeData.route,
822
+ children
823
+ });
824
+ if (!NextLinkComponent) return anchor;
825
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NextLinkComponent, {
826
+ route: routeData.route,
827
+ params: routeData.routeParams,
828
+ ...routeData.routeOptions || {},
829
+ children: anchor
830
+ });
831
+ };
832
+ var PaginationLink_default = PaginationLink;
833
+
834
+ //#endregion
835
+ //#region src/Pagination/ArrowButton.tsx
836
+ const ArrowButton = ({ type, routeData, nextLink, disabled = false, onClick }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PaginationLink_default, {
837
+ className: `kks-pagination__btn kks-pagination__arrow-btn kks-pagination__arrow-btn--${type}${disabled ? " kks-pagination__arrow-btn--hidden" : ""}`,
838
+ routeData,
839
+ nextLink,
840
+ rel: type,
841
+ "aria-label": type === "prev" ? "Previous page" : "Next page",
842
+ onClick,
843
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `kks-pagination__icon kks-pagination__icon-${type}` })
844
+ });
845
+ var ArrowButton_default = ArrowButton;
846
+
847
+ //#endregion
848
+ //#region src/Pagination/DotsButton.tsx
849
+ const DotsButton = ({ dots }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
850
+ className: "kks-pagination__dots",
851
+ children: dots
852
+ });
853
+ var DotsButton_default = DotsButton;
854
+
855
+ //#endregion
856
+ //#region src/Pagination/NumberButton.tsx
857
+ const NumberButton = ({ totalPages, pageNumber, routeData, nextLink, active = false, onClick }) => {
858
+ if (active) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
859
+ className: "kks-pagination__btn kks-pagination__num-btn kks-pagination__num-btn--active",
860
+ "data-total-pages": ` / ${totalPages}`,
861
+ "aria-current": "page",
862
+ children: pageNumber
863
+ });
864
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PaginationLink_default, {
865
+ className: "kks-pagination__btn kks-pagination__num-btn",
866
+ routeData,
867
+ nextLink,
868
+ onClick,
869
+ children: pageNumber
870
+ });
871
+ };
872
+ var NumberButton_default = NumberButton;
873
+
874
+ //#endregion
875
+ //#region src/Pagination/styles.ts
876
+ const paginationStyle = __emotion_react.css`
877
+ :where(&) {
878
+ display: inline-block;
879
+ }
880
+
881
+ :where(.kks-pagination__content) {
882
+ display: flex;
883
+ margin: 0;
884
+ padding-left: 0;
885
+ list-style: none;
886
+ }
887
+
888
+ :where(.kks-pagination__num-item:not(.kks-pagination__num-item--current)) {
889
+ display: none;
890
+ }
891
+
892
+ :where(.kks-pagination__btn) {
893
+ display: block;
894
+ margin: 0 12px;
895
+ padding: 0.64rem 0.8rem;
896
+ border-radius: 3px;
897
+ font-size: 1rem;
898
+ line-height: 1;
899
+ user-select: none;
900
+ }
901
+
902
+ :where(.kks-pagination__num-btn) {
903
+ text-decoration: none;
904
+ color: #666;
905
+ }
906
+
907
+ :where(.kks-pagination__num-btn)::after {
908
+ content: attr(data-total-pages);
909
+ }
910
+
911
+ :where(.kks-pagination__arrow-btn--prev) {
912
+ margin-left: 0;
913
+ }
914
+
915
+ :where(.kks-pagination__arrow-btn--next) {
916
+ margin-right: 0;
917
+ }
918
+
919
+ :where(.kks-pagination__arrow-btn--hidden) {
920
+ display: none;
921
+ }
922
+
923
+ :where(.kks-pagination__icon) {
924
+ display: block;
925
+ width: 1rem;
926
+ height: 1rem;
927
+ background-size: contain;
928
+ }
929
+
930
+ :where(.kks-pagination__icon-prev) {
931
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 129 129'%3E%3Cpath fill='%23666' d='M88.6 121.3a4.1 4.1 0 0 1-5.8 0L28.9 67.4a4.1 4.1 0 0 1 0-5.8L82.8 7.7a4.1 4.1 0 0 1 5.8 5.8l-51 51 51 51a4.1 4.1 0 0 1 0 5.8z'/%3E%3C/svg%3E");
932
+ }
933
+
934
+ :where(.kks-pagination__icon-next) {
935
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 129 129'%3E%3Cpath fill='%23666' d='M40.4 121.3a4.1 4.1 0 0 1 0-5.8l51-51-51-51a4.1 4.1 0 0 1 5.8-5.8l53.9 53.9a4.1 4.1 0 0 1 0 5.8l-53.9 53.9a4.1 4.1 0 0 1-5.8 0z'/%3E%3C/svg%3E");
936
+ }
937
+
938
+ :where(.kks-pagination__dots) {
939
+ display: block;
940
+ margin: 0 12px;
941
+ padding: 0.64rem 0;
942
+ font-size: 1rem;
943
+ line-height: 50%;
944
+ color: #aaa;
945
+ user-select: none;
946
+ }
947
+
948
+ @media (min-width: 768px) {
949
+ :where(.kks-pagination__num-item:not(.kks-pagination__num-item--current)) {
950
+ display: block;
951
+ }
952
+
953
+ :where(.kks-pagination__btn),
954
+ :where(.kks-pagination__dots) {
955
+ margin: 0 6px;
956
+ }
957
+
958
+ :where(.kks-pagination__num-btn)::after {
959
+ content: "";
960
+ }
961
+
962
+ :where(.kks-pagination__num-btn:hover:not(.kks-pagination__num-btn--active)),
963
+ :where(.kks-pagination__arrow-btn:hover) {
964
+ background: rgba(0, 161, 179, 0.1);
965
+ }
966
+
967
+ :where(.kks-pagination__num-btn--active) {
968
+ font-weight: 500;
969
+ color: #fff;
970
+ background: #00a1b3;
971
+ }
972
+ }
973
+ `;
974
+
975
+ //#endregion
976
+ //#region src/Pagination/index.tsx
977
+ const DEFAULT_CURRENT_PAGE = 1;
978
+ const DEFAULT_PAGE_NEIGHBOURS = 2;
979
+ const ROUTE_URL_REPLACE_STRING = "$(pageNumber)";
980
+ const range = (start, end) => Array.from({ length: end - start + 1 }, (_, index) => start + index);
981
+ const getPageNumbers = (currentPage, pageNeighbours, totalPages) => {
982
+ const numberBlocks = pageNeighbours * 2 + 3;
983
+ if (totalPages <= numberBlocks + 2) return range(1, totalPages);
984
+ const startPage = Math.max(2, currentPage - pageNeighbours);
985
+ const endPage = Math.min(totalPages - 1, currentPage + pageNeighbours);
986
+ let pages = range(startPage, endPage);
987
+ const hasLeftDots = startPage > 2;
988
+ const hasRightDots = totalPages - endPage > 1;
989
+ const offset = numberBlocks - (pages.length + 2);
990
+ if (hasLeftDots && !hasRightDots) pages = [
991
+ "...",
992
+ ...range(startPage - offset, startPage - 1),
993
+ ...pages
994
+ ];
995
+ else if (!hasLeftDots && hasRightDots) pages = [
996
+ ...pages,
997
+ ...range(endPage + 1, endPage + offset),
998
+ "..."
999
+ ];
1000
+ else pages = [
1001
+ "...",
1002
+ ...pages,
1003
+ "..."
1004
+ ];
1005
+ return [
1006
+ 1,
1007
+ ...pages,
1008
+ totalPages
1009
+ ];
1010
+ };
1011
+ const toPositiveInt = (value, fallback) => {
1012
+ const parsed = Number(value);
1013
+ return parsed > 0 ? parsed : fallback;
1014
+ };
1015
+ const toValidPage = (value, totalPages) => {
1016
+ const page = Number(value);
1017
+ return Number.isNaN(page) || page < 1 || page > totalPages ? DEFAULT_CURRENT_PAGE : page;
1018
+ };
1019
+ const normalizePaginationParams = ({ totalCount: rawTotalCount, perPageCount: rawPerPageCount, currentPage: rawCurrentPage = DEFAULT_CURRENT_PAGE, pageNeighbours: rawPageNeighbours = DEFAULT_PAGE_NEIGHBOURS }) => {
1020
+ const totalCount = Number(rawTotalCount);
1021
+ const perPageCount = Number(rawPerPageCount);
1022
+ const totalPages = totalCount && perPageCount ? Math.ceil(totalCount / perPageCount) : 1;
1023
+ return {
1024
+ totalCount,
1025
+ perPageCount,
1026
+ totalPages,
1027
+ currentPage: toValidPage(rawCurrentPage, totalPages),
1028
+ pageNeighbours: toPositiveInt(rawPageNeighbours, DEFAULT_PAGE_NEIGHBOURS)
1029
+ };
1030
+ };
1031
+ const renderEllipsis = (ellipsis, index) => /* @__PURE__ */ (0, __emotion_react_jsx_runtime.jsx)("li", {
1032
+ className: "kks-pagination__num-item",
1033
+ children: /* @__PURE__ */ (0, __emotion_react_jsx_runtime.jsx)(DotsButton_default, { dots: ellipsis })
1034
+ }, `dots-${index}`);
1035
+ const Pagination = (props) => {
1036
+ const { totalCount, perPageCount, totalPages, currentPage, pageNeighbours } = normalizePaginationParams(props);
1037
+ const { route = null, routeParams = null, routeOptions = null, nextRouter = null, nextLink = null, className = "", onPageChange = () => {} } = props;
1038
+ if (totalPages === 1) return null;
1039
+ const getRouteData = (page) => {
1040
+ if (!route) return { route: "#" };
1041
+ const nextRouteParams = routeParams ? Object.fromEntries(Object.entries(routeParams).map(([key, value]) => [key, value === ROUTE_URL_REPLACE_STRING ? page : value])) : null;
1042
+ if (nextRouteParams && !Object.values(routeParams).includes(ROUTE_URL_REPLACE_STRING)) nextRouteParams.page = page;
1043
+ return {
1044
+ route: route.replace(ROUTE_URL_REPLACE_STRING, String(page)),
1045
+ routeParams: nextRouteParams,
1046
+ routeOptions
1047
+ };
1048
+ };
1049
+ const handleClick = (page, routeData) => (event) => {
1050
+ event.preventDefault();
1051
+ const isNavigable = routeData.route !== "#";
1052
+ onPageChange({
1053
+ currentPage: page,
1054
+ totalPages,
1055
+ totalCount,
1056
+ perPageCount,
1057
+ route: isNavigable ? routeData.route : null,
1058
+ ...isNavigable ? routeData : {}
1059
+ });
1060
+ if (nextRouter && isNavigable) nextRouter.pushRoute(routeData.route, routeData.routeParams || {}, routeData.routeOptions || {});
1061
+ };
1062
+ const previousPage = currentPage === 1 ? 1 : currentPage - 1;
1063
+ const nextPage = currentPage === totalPages ? totalPages : currentPage + 1;
1064
+ const renderArrow = (type, page, disabled) => {
1065
+ const routeData = getRouteData(page);
1066
+ return /* @__PURE__ */ (0, __emotion_react_jsx_runtime.jsx)(ArrowButton_default, {
1067
+ type,
1068
+ routeData,
1069
+ nextLink,
1070
+ disabled,
1071
+ onClick: handleClick(page, routeData)
1072
+ });
1073
+ };
1074
+ const renderPageNumber = (page) => {
1075
+ const routeData = getRouteData(page);
1076
+ const isCurrentPage = page === currentPage;
1077
+ return /* @__PURE__ */ (0, __emotion_react_jsx_runtime.jsx)("li", {
1078
+ className: `kks-pagination__num-item${isCurrentPage ? " kks-pagination__num-item--current" : ""}`,
1079
+ children: /* @__PURE__ */ (0, __emotion_react_jsx_runtime.jsx)(NumberButton_default, {
1080
+ totalPages,
1081
+ pageNumber: page,
1082
+ routeData,
1083
+ nextLink,
1084
+ active: isCurrentPage,
1085
+ onClick: handleClick(page, routeData)
1086
+ })
1087
+ }, `page-${page}`);
1088
+ };
1089
+ return /* @__PURE__ */ (0, __emotion_react_jsx_runtime.jsx)("nav", {
1090
+ css: paginationStyle,
1091
+ className: `kks-pagination ${className}`,
1092
+ "aria-label": "Page navigation",
1093
+ children: /* @__PURE__ */ (0, __emotion_react_jsx_runtime.jsxs)("ul", {
1094
+ className: "kks-pagination__content",
1095
+ children: [
1096
+ /* @__PURE__ */ (0, __emotion_react_jsx_runtime.jsx)("li", { children: renderArrow("prev", previousPage, currentPage === 1) }),
1097
+ getPageNumbers(currentPage, pageNeighbours, totalPages).map((page, index) => typeof page === "number" ? renderPageNumber(page) : renderEllipsis(page, index)),
1098
+ /* @__PURE__ */ (0, __emotion_react_jsx_runtime.jsx)("li", { children: renderArrow("next", nextPage, currentPage === totalPages) })
1099
+ ]
1100
+ })
1101
+ });
1102
+ };
1103
+ var Pagination_default = Pagination;
1104
+
715
1105
  //#endregion
716
1106
  //#region src/icons/see-more.svg
717
1107
  var see_more_default = "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSI4IiBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMTQgOCI+CiAgPHBhdGggc3Ryb2tlPSIjY2NjIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iMS41IiBkPSJNLjc1Ljc1IDYuNyA2LjY4cS4wNy4wNi4xNCAwbTAgMHEwIDAgMCAwWm0wIDBMMTIuNzUuNzUiLz4KPC9zdmc+";
@@ -1083,6 +1473,8 @@ exports.Banner = Banner_default;
1083
1473
  exports.Dropdown = Dropdown_default;
1084
1474
  exports.HiddenToggle = HiddenToggle_default;
1085
1475
  exports.Icon = Icon_default;
1476
+ exports.Modal = Modal_default;
1477
+ exports.Pagination = Pagination_default;
1086
1478
  exports.SeeMore = SeeMore_default;
1087
1479
  exports.VideoItem = VideoItem_default;
1088
1480
  exports.VideoThumbnail = VideoThumbnail_default;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react0 from "react";
2
- import { ElementType, MouseEventHandler, ReactElement, ReactNode } from "react";
2
+ import { ComponentType, ElementType, MouseEventHandler, ReactElement, ReactNode } from "react";
3
3
  import * as _emotion_react_jsx_runtime0 from "@emotion/react/jsx-runtime";
4
4
  import { CSSObject } from "@emotion/react";
5
5
  import Link from "next/link";
@@ -76,6 +76,54 @@ declare const Icon: ({
76
76
  src: any;
77
77
  }) => _emotion_react_jsx_runtime0.JSX.Element;
78
78
  //#endregion
79
+ //#region src/Modal/index.d.ts
80
+ type ModalProps = {
81
+ children: ReactNode;
82
+ className?: string;
83
+ isHideModalOnClickOverlay?: boolean;
84
+ isShow: boolean;
85
+ onHidden?: () => void;
86
+ onShown?: () => void;
87
+ onTriggerHiding?: () => void;
88
+ };
89
+ declare const Modal: react0.MemoExoticComponent<react0.ForwardRefExoticComponent<ModalProps & react0.RefAttributes<HTMLDivElement>>>;
90
+ //#endregion
91
+ //#region src/Pagination/types.d.ts
92
+ type RouteParams = Record<string, unknown>;
93
+ type RouteOptions = Record<string, unknown>;
94
+ type NextLinkProps = {
95
+ route: string;
96
+ params?: RouteParams | null;
97
+ children: ReactNode;
98
+ } & RouteOptions;
99
+ type NextLink = ComponentType<NextLinkProps>;
100
+ //#endregion
101
+ //#region src/Pagination/index.d.ts
102
+ type PaginationProps = {
103
+ totalCount: number | string;
104
+ perPageCount: number | string;
105
+ currentPage?: number | string;
106
+ pageNeighbours?: number | string;
107
+ route?: string | null;
108
+ routeParams?: RouteParams | null;
109
+ routeOptions?: RouteOptions | null;
110
+ nextRouter?: {
111
+ pushRoute: (route: string, routeParams?: RouteParams, routeOptions?: RouteOptions) => void;
112
+ } | null;
113
+ nextLink?: NextLink | null;
114
+ className?: string;
115
+ onPageChange?: (page: {
116
+ currentPage: number;
117
+ totalPages: number;
118
+ totalCount: number;
119
+ perPageCount: number;
120
+ route: string | null;
121
+ routeParams?: RouteParams | null;
122
+ routeOptions?: RouteOptions | null;
123
+ }) => void;
124
+ };
125
+ declare const Pagination: (props: PaginationProps) => ReactElement | null;
126
+ //#endregion
79
127
  //#region src/SeeMore.d.ts
80
128
  declare const defaultMessages: {
81
129
  seeMore: string;
@@ -156,4 +204,4 @@ declare const VideoThumbnail: ({
156
204
  onClick?: MouseEventHandler<HTMLAnchorElement>;
157
205
  }) => _emotion_react_jsx_runtime0.JSX.Element;
158
206
  //#endregion
159
- export { Banner, Dropdown, HiddenToggle, Icon, SeeMore, VideoItem, VideoThumbnail };
207
+ export { Banner, Dropdown, HiddenToggle, Icon, Modal, Pagination, SeeMore, VideoItem, VideoThumbnail };
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react0 from "react";
2
- import { ElementType, MouseEventHandler, ReactElement, ReactNode } from "react";
3
- import * as _emotion_react_jsx_runtime2 from "@emotion/react/jsx-runtime";
2
+ import { ComponentType, ElementType, MouseEventHandler, ReactElement, ReactNode } from "react";
3
+ import * as _emotion_react_jsx_runtime0 from "@emotion/react/jsx-runtime";
4
4
  import { CSSObject } from "@emotion/react";
5
5
  import Link from "next/link";
6
6
 
@@ -65,7 +65,7 @@ declare const HiddenToggle: ({
65
65
  defaultEnabled?: boolean;
66
66
  children: any;
67
67
  onChange: any;
68
- }) => _emotion_react_jsx_runtime2.JSX.Element;
68
+ }) => _emotion_react_jsx_runtime0.JSX.Element;
69
69
  //#endregion
70
70
  //#region src/Icon.d.ts
71
71
  declare const Icon: ({
@@ -74,7 +74,55 @@ declare const Icon: ({
74
74
  }: {
75
75
  style?: {};
76
76
  src: any;
77
- }) => _emotion_react_jsx_runtime2.JSX.Element;
77
+ }) => _emotion_react_jsx_runtime0.JSX.Element;
78
+ //#endregion
79
+ //#region src/Modal/index.d.ts
80
+ type ModalProps = {
81
+ children: ReactNode;
82
+ className?: string;
83
+ isHideModalOnClickOverlay?: boolean;
84
+ isShow: boolean;
85
+ onHidden?: () => void;
86
+ onShown?: () => void;
87
+ onTriggerHiding?: () => void;
88
+ };
89
+ declare const Modal: react0.MemoExoticComponent<react0.ForwardRefExoticComponent<ModalProps & react0.RefAttributes<HTMLDivElement>>>;
90
+ //#endregion
91
+ //#region src/Pagination/types.d.ts
92
+ type RouteParams = Record<string, unknown>;
93
+ type RouteOptions = Record<string, unknown>;
94
+ type NextLinkProps = {
95
+ route: string;
96
+ params?: RouteParams | null;
97
+ children: ReactNode;
98
+ } & RouteOptions;
99
+ type NextLink = ComponentType<NextLinkProps>;
100
+ //#endregion
101
+ //#region src/Pagination/index.d.ts
102
+ type PaginationProps = {
103
+ totalCount: number | string;
104
+ perPageCount: number | string;
105
+ currentPage?: number | string;
106
+ pageNeighbours?: number | string;
107
+ route?: string | null;
108
+ routeParams?: RouteParams | null;
109
+ routeOptions?: RouteOptions | null;
110
+ nextRouter?: {
111
+ pushRoute: (route: string, routeParams?: RouteParams, routeOptions?: RouteOptions) => void;
112
+ } | null;
113
+ nextLink?: NextLink | null;
114
+ className?: string;
115
+ onPageChange?: (page: {
116
+ currentPage: number;
117
+ totalPages: number;
118
+ totalCount: number;
119
+ perPageCount: number;
120
+ route: string | null;
121
+ routeParams?: RouteParams | null;
122
+ routeOptions?: RouteOptions | null;
123
+ }) => void;
124
+ };
125
+ declare const Pagination: (props: PaginationProps) => ReactElement | null;
78
126
  //#endregion
79
127
  //#region src/SeeMore.d.ts
80
128
  declare const defaultMessages: {
@@ -90,7 +138,7 @@ declare const SeeMore: ({
90
138
  style?: CSSObject;
91
139
  messages?: typeof defaultMessages;
92
140
  children?: ReactNode;
93
- }) => _emotion_react_jsx_runtime2.JSX.Element;
141
+ }) => _emotion_react_jsx_runtime0.JSX.Element;
94
142
  //#endregion
95
143
  //#region src/VideoItem.d.ts
96
144
  type VideoDetail = {
@@ -154,6 +202,6 @@ declare const VideoThumbnail: ({
154
202
  Image?: ElementType;
155
203
  };
156
204
  onClick?: MouseEventHandler<HTMLAnchorElement>;
157
- }) => _emotion_react_jsx_runtime2.JSX.Element;
205
+ }) => _emotion_react_jsx_runtime0.JSX.Element;
158
206
  //#endregion
159
- export { Banner, Dropdown, HiddenToggle, Icon, SeeMore, VideoItem, VideoThumbnail };
207
+ export { Banner, Dropdown, HiddenToggle, Icon, Modal, Pagination, SeeMore, VideoItem, VideoThumbnail };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { Children, forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
1
+ import { Children, forwardRef, memo, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import { jsx as jsx$1, jsxs } from "@emotion/react/jsx-runtime";
4
4
  import { css } from "@emotion/react";
@@ -712,6 +712,396 @@ const Icon = ({ style = {}, src }) => /* @__PURE__ */ jsx$1("span", { css: [
712
712
  ] });
713
713
  var Icon_default = Icon;
714
714
 
715
+ //#endregion
716
+ //#region src/Modal/styles.ts
717
+ const modalStyle = css`
718
+ :where(&) {
719
+ position: fixed;
720
+ top: 0;
721
+ right: 0;
722
+ bottom: 0;
723
+ left: 0;
724
+ z-index: 1000;
725
+ padding: 1rem;
726
+ overflow-x: hidden;
727
+ overflow-y: auto;
728
+ visibility: hidden;
729
+ opacity: 0;
730
+ background-color: rgba(0, 0, 0, 0.7);
731
+ transition: opacity 0.3s;
732
+ }
733
+
734
+ :where(&.modal--show),
735
+ :where(&.modal--shown) {
736
+ display: flex;
737
+ justify-content: center;
738
+ visibility: visible;
739
+ opacity: 1;
740
+ }
741
+
742
+ :where(.modal__content) {
743
+ width: 100%;
744
+ max-width: 700px;
745
+ margin: auto 0;
746
+ }
747
+ `;
748
+
749
+ //#endregion
750
+ //#region src/Modal/index.tsx
751
+ const MODAL_STATE = {
752
+ SHOW: "show",
753
+ SHOWN: "shown",
754
+ HIDE: "hide",
755
+ HIDDEN: "hidden"
756
+ };
757
+ const isShowing = (state) => state === MODAL_STATE.SHOW || state === MODAL_STATE.SHOWN;
758
+ const Modal = memo(forwardRef(function Modal$1({ children, className = "", isHideModalOnClickOverlay = true, isShow, onHidden = () => {}, onShown = () => {}, onTriggerHiding = () => {} }, ref) {
759
+ const [visualState, setVisualState] = useState(MODAL_STATE.HIDDEN);
760
+ const shouldLockBody = isShowing(visualState);
761
+ if (isShow && !isShowing(visualState)) setVisualState(MODAL_STATE.SHOW);
762
+ else if (!isShow && isShowing(visualState)) setVisualState(MODAL_STATE.HIDE);
763
+ useLayoutEffect(() => {
764
+ if (!shouldLockBody) return;
765
+ const previousOverflow = document.body.style.overflow;
766
+ document.body.style.overflow = "hidden";
767
+ return () => {
768
+ document.body.style.overflow = previousOverflow;
769
+ };
770
+ }, [shouldLockBody]);
771
+ const previousVisualState = useRef(visualState);
772
+ useLayoutEffect(() => {
773
+ if (previousVisualState.current === visualState) return;
774
+ previousVisualState.current = visualState;
775
+ if (visualState === MODAL_STATE.SHOWN) onShown();
776
+ else if (visualState === MODAL_STATE.HIDDEN) onHidden();
777
+ }, [
778
+ visualState,
779
+ onShown,
780
+ onHidden
781
+ ]);
782
+ const handleAnimationEnd = useCallback(() => {
783
+ setVisualState((previousState) => {
784
+ if (previousState === MODAL_STATE.SHOW) return MODAL_STATE.SHOWN;
785
+ if (previousState === MODAL_STATE.HIDE) return MODAL_STATE.HIDDEN;
786
+ return previousState;
787
+ });
788
+ }, []);
789
+ const handleClick = useCallback((event) => {
790
+ if (event.target.getAttribute("data-dismiss") === "modal") onTriggerHiding();
791
+ }, [onTriggerHiding]);
792
+ const handleKeyDown = useCallback((event) => {
793
+ if (event.key !== "Enter" && event.key !== " ") return;
794
+ if (event.target.getAttribute("data-dismiss") !== "modal") return;
795
+ event.preventDefault();
796
+ onTriggerHiding();
797
+ }, [onTriggerHiding]);
798
+ return /* @__PURE__ */ jsx$1("div", {
799
+ css: modalStyle,
800
+ ref,
801
+ role: "presentation",
802
+ className: `modal modal--${visualState} ${className}`,
803
+ onClick: handleClick,
804
+ onKeyDown: handleKeyDown,
805
+ onAnimationEnd: handleAnimationEnd,
806
+ onTransitionEnd: handleAnimationEnd,
807
+ "data-dismiss": isHideModalOnClickOverlay ? "modal" : void 0,
808
+ children: /* @__PURE__ */ jsx$1("div", {
809
+ className: "modal__content",
810
+ children
811
+ })
812
+ });
813
+ }));
814
+ var Modal_default = Modal;
815
+
816
+ //#endregion
817
+ //#region src/Pagination/PaginationLink.tsx
818
+ const PaginationLink = ({ routeData, nextLink: NextLinkComponent, children, ...anchorProps }) => {
819
+ const anchor = /* @__PURE__ */ jsx("a", {
820
+ ...anchorProps,
821
+ href: NextLinkComponent ? void 0 : routeData.route,
822
+ children
823
+ });
824
+ if (!NextLinkComponent) return anchor;
825
+ return /* @__PURE__ */ jsx(NextLinkComponent, {
826
+ route: routeData.route,
827
+ params: routeData.routeParams,
828
+ ...routeData.routeOptions || {},
829
+ children: anchor
830
+ });
831
+ };
832
+ var PaginationLink_default = PaginationLink;
833
+
834
+ //#endregion
835
+ //#region src/Pagination/ArrowButton.tsx
836
+ const ArrowButton = ({ type, routeData, nextLink, disabled = false, onClick }) => /* @__PURE__ */ jsx(PaginationLink_default, {
837
+ className: `kks-pagination__btn kks-pagination__arrow-btn kks-pagination__arrow-btn--${type}${disabled ? " kks-pagination__arrow-btn--hidden" : ""}`,
838
+ routeData,
839
+ nextLink,
840
+ rel: type,
841
+ "aria-label": type === "prev" ? "Previous page" : "Next page",
842
+ onClick,
843
+ children: /* @__PURE__ */ jsx("span", { className: `kks-pagination__icon kks-pagination__icon-${type}` })
844
+ });
845
+ var ArrowButton_default = ArrowButton;
846
+
847
+ //#endregion
848
+ //#region src/Pagination/DotsButton.tsx
849
+ const DotsButton = ({ dots }) => /* @__PURE__ */ jsx("span", {
850
+ className: "kks-pagination__dots",
851
+ children: dots
852
+ });
853
+ var DotsButton_default = DotsButton;
854
+
855
+ //#endregion
856
+ //#region src/Pagination/NumberButton.tsx
857
+ const NumberButton = ({ totalPages, pageNumber, routeData, nextLink, active = false, onClick }) => {
858
+ if (active) return /* @__PURE__ */ jsx("span", {
859
+ className: "kks-pagination__btn kks-pagination__num-btn kks-pagination__num-btn--active",
860
+ "data-total-pages": ` / ${totalPages}`,
861
+ "aria-current": "page",
862
+ children: pageNumber
863
+ });
864
+ return /* @__PURE__ */ jsx(PaginationLink_default, {
865
+ className: "kks-pagination__btn kks-pagination__num-btn",
866
+ routeData,
867
+ nextLink,
868
+ onClick,
869
+ children: pageNumber
870
+ });
871
+ };
872
+ var NumberButton_default = NumberButton;
873
+
874
+ //#endregion
875
+ //#region src/Pagination/styles.ts
876
+ const paginationStyle = css`
877
+ :where(&) {
878
+ display: inline-block;
879
+ }
880
+
881
+ :where(.kks-pagination__content) {
882
+ display: flex;
883
+ margin: 0;
884
+ padding-left: 0;
885
+ list-style: none;
886
+ }
887
+
888
+ :where(.kks-pagination__num-item:not(.kks-pagination__num-item--current)) {
889
+ display: none;
890
+ }
891
+
892
+ :where(.kks-pagination__btn) {
893
+ display: block;
894
+ margin: 0 12px;
895
+ padding: 0.64rem 0.8rem;
896
+ border-radius: 3px;
897
+ font-size: 1rem;
898
+ line-height: 1;
899
+ user-select: none;
900
+ }
901
+
902
+ :where(.kks-pagination__num-btn) {
903
+ text-decoration: none;
904
+ color: #666;
905
+ }
906
+
907
+ :where(.kks-pagination__num-btn)::after {
908
+ content: attr(data-total-pages);
909
+ }
910
+
911
+ :where(.kks-pagination__arrow-btn--prev) {
912
+ margin-left: 0;
913
+ }
914
+
915
+ :where(.kks-pagination__arrow-btn--next) {
916
+ margin-right: 0;
917
+ }
918
+
919
+ :where(.kks-pagination__arrow-btn--hidden) {
920
+ display: none;
921
+ }
922
+
923
+ :where(.kks-pagination__icon) {
924
+ display: block;
925
+ width: 1rem;
926
+ height: 1rem;
927
+ background-size: contain;
928
+ }
929
+
930
+ :where(.kks-pagination__icon-prev) {
931
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 129 129'%3E%3Cpath fill='%23666' d='M88.6 121.3a4.1 4.1 0 0 1-5.8 0L28.9 67.4a4.1 4.1 0 0 1 0-5.8L82.8 7.7a4.1 4.1 0 0 1 5.8 5.8l-51 51 51 51a4.1 4.1 0 0 1 0 5.8z'/%3E%3C/svg%3E");
932
+ }
933
+
934
+ :where(.kks-pagination__icon-next) {
935
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 129 129'%3E%3Cpath fill='%23666' d='M40.4 121.3a4.1 4.1 0 0 1 0-5.8l51-51-51-51a4.1 4.1 0 0 1 5.8-5.8l53.9 53.9a4.1 4.1 0 0 1 0 5.8l-53.9 53.9a4.1 4.1 0 0 1-5.8 0z'/%3E%3C/svg%3E");
936
+ }
937
+
938
+ :where(.kks-pagination__dots) {
939
+ display: block;
940
+ margin: 0 12px;
941
+ padding: 0.64rem 0;
942
+ font-size: 1rem;
943
+ line-height: 50%;
944
+ color: #aaa;
945
+ user-select: none;
946
+ }
947
+
948
+ @media (min-width: 768px) {
949
+ :where(.kks-pagination__num-item:not(.kks-pagination__num-item--current)) {
950
+ display: block;
951
+ }
952
+
953
+ :where(.kks-pagination__btn),
954
+ :where(.kks-pagination__dots) {
955
+ margin: 0 6px;
956
+ }
957
+
958
+ :where(.kks-pagination__num-btn)::after {
959
+ content: "";
960
+ }
961
+
962
+ :where(.kks-pagination__num-btn:hover:not(.kks-pagination__num-btn--active)),
963
+ :where(.kks-pagination__arrow-btn:hover) {
964
+ background: rgba(0, 161, 179, 0.1);
965
+ }
966
+
967
+ :where(.kks-pagination__num-btn--active) {
968
+ font-weight: 500;
969
+ color: #fff;
970
+ background: #00a1b3;
971
+ }
972
+ }
973
+ `;
974
+
975
+ //#endregion
976
+ //#region src/Pagination/index.tsx
977
+ const DEFAULT_CURRENT_PAGE = 1;
978
+ const DEFAULT_PAGE_NEIGHBOURS = 2;
979
+ const ROUTE_URL_REPLACE_STRING = "$(pageNumber)";
980
+ const range = (start, end) => Array.from({ length: end - start + 1 }, (_, index) => start + index);
981
+ const getPageNumbers = (currentPage, pageNeighbours, totalPages) => {
982
+ const numberBlocks = pageNeighbours * 2 + 3;
983
+ if (totalPages <= numberBlocks + 2) return range(1, totalPages);
984
+ const startPage = Math.max(2, currentPage - pageNeighbours);
985
+ const endPage = Math.min(totalPages - 1, currentPage + pageNeighbours);
986
+ let pages = range(startPage, endPage);
987
+ const hasLeftDots = startPage > 2;
988
+ const hasRightDots = totalPages - endPage > 1;
989
+ const offset = numberBlocks - (pages.length + 2);
990
+ if (hasLeftDots && !hasRightDots) pages = [
991
+ "...",
992
+ ...range(startPage - offset, startPage - 1),
993
+ ...pages
994
+ ];
995
+ else if (!hasLeftDots && hasRightDots) pages = [
996
+ ...pages,
997
+ ...range(endPage + 1, endPage + offset),
998
+ "..."
999
+ ];
1000
+ else pages = [
1001
+ "...",
1002
+ ...pages,
1003
+ "..."
1004
+ ];
1005
+ return [
1006
+ 1,
1007
+ ...pages,
1008
+ totalPages
1009
+ ];
1010
+ };
1011
+ const toPositiveInt = (value, fallback) => {
1012
+ const parsed = Number(value);
1013
+ return parsed > 0 ? parsed : fallback;
1014
+ };
1015
+ const toValidPage = (value, totalPages) => {
1016
+ const page = Number(value);
1017
+ return Number.isNaN(page) || page < 1 || page > totalPages ? DEFAULT_CURRENT_PAGE : page;
1018
+ };
1019
+ const normalizePaginationParams = ({ totalCount: rawTotalCount, perPageCount: rawPerPageCount, currentPage: rawCurrentPage = DEFAULT_CURRENT_PAGE, pageNeighbours: rawPageNeighbours = DEFAULT_PAGE_NEIGHBOURS }) => {
1020
+ const totalCount = Number(rawTotalCount);
1021
+ const perPageCount = Number(rawPerPageCount);
1022
+ const totalPages = totalCount && perPageCount ? Math.ceil(totalCount / perPageCount) : 1;
1023
+ return {
1024
+ totalCount,
1025
+ perPageCount,
1026
+ totalPages,
1027
+ currentPage: toValidPage(rawCurrentPage, totalPages),
1028
+ pageNeighbours: toPositiveInt(rawPageNeighbours, DEFAULT_PAGE_NEIGHBOURS)
1029
+ };
1030
+ };
1031
+ const renderEllipsis = (ellipsis, index) => /* @__PURE__ */ jsx$1("li", {
1032
+ className: "kks-pagination__num-item",
1033
+ children: /* @__PURE__ */ jsx$1(DotsButton_default, { dots: ellipsis })
1034
+ }, `dots-${index}`);
1035
+ const Pagination = (props) => {
1036
+ const { totalCount, perPageCount, totalPages, currentPage, pageNeighbours } = normalizePaginationParams(props);
1037
+ const { route = null, routeParams = null, routeOptions = null, nextRouter = null, nextLink = null, className = "", onPageChange = () => {} } = props;
1038
+ if (totalPages === 1) return null;
1039
+ const getRouteData = (page) => {
1040
+ if (!route) return { route: "#" };
1041
+ const nextRouteParams = routeParams ? Object.fromEntries(Object.entries(routeParams).map(([key, value]) => [key, value === ROUTE_URL_REPLACE_STRING ? page : value])) : null;
1042
+ if (nextRouteParams && !Object.values(routeParams).includes(ROUTE_URL_REPLACE_STRING)) nextRouteParams.page = page;
1043
+ return {
1044
+ route: route.replace(ROUTE_URL_REPLACE_STRING, String(page)),
1045
+ routeParams: nextRouteParams,
1046
+ routeOptions
1047
+ };
1048
+ };
1049
+ const handleClick = (page, routeData) => (event) => {
1050
+ event.preventDefault();
1051
+ const isNavigable = routeData.route !== "#";
1052
+ onPageChange({
1053
+ currentPage: page,
1054
+ totalPages,
1055
+ totalCount,
1056
+ perPageCount,
1057
+ route: isNavigable ? routeData.route : null,
1058
+ ...isNavigable ? routeData : {}
1059
+ });
1060
+ if (nextRouter && isNavigable) nextRouter.pushRoute(routeData.route, routeData.routeParams || {}, routeData.routeOptions || {});
1061
+ };
1062
+ const previousPage = currentPage === 1 ? 1 : currentPage - 1;
1063
+ const nextPage = currentPage === totalPages ? totalPages : currentPage + 1;
1064
+ const renderArrow = (type, page, disabled) => {
1065
+ const routeData = getRouteData(page);
1066
+ return /* @__PURE__ */ jsx$1(ArrowButton_default, {
1067
+ type,
1068
+ routeData,
1069
+ nextLink,
1070
+ disabled,
1071
+ onClick: handleClick(page, routeData)
1072
+ });
1073
+ };
1074
+ const renderPageNumber = (page) => {
1075
+ const routeData = getRouteData(page);
1076
+ const isCurrentPage = page === currentPage;
1077
+ return /* @__PURE__ */ jsx$1("li", {
1078
+ className: `kks-pagination__num-item${isCurrentPage ? " kks-pagination__num-item--current" : ""}`,
1079
+ children: /* @__PURE__ */ jsx$1(NumberButton_default, {
1080
+ totalPages,
1081
+ pageNumber: page,
1082
+ routeData,
1083
+ nextLink,
1084
+ active: isCurrentPage,
1085
+ onClick: handleClick(page, routeData)
1086
+ })
1087
+ }, `page-${page}`);
1088
+ };
1089
+ return /* @__PURE__ */ jsx$1("nav", {
1090
+ css: paginationStyle,
1091
+ className: `kks-pagination ${className}`,
1092
+ "aria-label": "Page navigation",
1093
+ children: /* @__PURE__ */ jsxs("ul", {
1094
+ className: "kks-pagination__content",
1095
+ children: [
1096
+ /* @__PURE__ */ jsx$1("li", { children: renderArrow("prev", previousPage, currentPage === 1) }),
1097
+ getPageNumbers(currentPage, pageNeighbours, totalPages).map((page, index) => typeof page === "number" ? renderPageNumber(page) : renderEllipsis(page, index)),
1098
+ /* @__PURE__ */ jsx$1("li", { children: renderArrow("next", nextPage, currentPage === totalPages) })
1099
+ ]
1100
+ })
1101
+ });
1102
+ };
1103
+ var Pagination_default = Pagination;
1104
+
715
1105
  //#endregion
716
1106
  //#region src/icons/see-more.svg
717
1107
  var see_more_default = "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNCIgaGVpZ2h0PSI4IiBmaWxsPSJub25lIiB2aWV3Qm94PSIwIDAgMTQgOCI+CiAgPHBhdGggc3Ryb2tlPSIjY2NjIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS13aWR0aD0iMS41IiBkPSJNLjc1Ljc1IDYuNyA2LjY4cS4wNy4wNi4xNCAwbTAgMHEwIDAgMCAwWm0wIDBMMTIuNzUuNzUiLz4KPC9zdmc+";
@@ -1079,4 +1469,4 @@ const VideoItem = ({ style, href = "", thumbnailHref = href, imageUrl = "", data
1079
1469
  var VideoItem_default = VideoItem;
1080
1470
 
1081
1471
  //#endregion
1082
- export { Banner_default as Banner, Dropdown_default as Dropdown, HiddenToggle_default as HiddenToggle, Icon_default as Icon, SeeMore_default as SeeMore, VideoItem_default as VideoItem, VideoThumbnail_default as VideoThumbnail };
1472
+ export { Banner_default as Banner, Dropdown_default as Dropdown, HiddenToggle_default as HiddenToggle, Icon_default as Icon, Modal_default as Modal, Pagination_default as Pagination, SeeMore_default as SeeMore, VideoItem_default as VideoItem, VideoThumbnail_default as VideoThumbnail };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kkcompany/app-ui",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
4
4
  "module": "dist/index.mjs",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.mts",
@@ -19,7 +19,8 @@
19
19
  "license": "MIT",
20
20
  "scripts": {
21
21
  "dev": "next",
22
- "build": "tsdown --format esm --format cjs"
22
+ "build": "tsdown --format esm --format cjs",
23
+ "test": "vitest run"
23
24
  },
24
25
  "dependencies": {
25
26
  "@emotion/react": "^11.14.0"
@@ -30,10 +31,13 @@
30
31
  },
31
32
  "devDependencies": {
32
33
  "@biomejs/biome": "^2.3.5",
34
+ "@testing-library/react": "^13.4.0",
33
35
  "binary-base64-loader": "^1.0.0",
36
+ "jsdom": "^26.1.0",
34
37
  "next": "^16.0.2",
35
38
  "rollup-plugin-base64": "^1.0.1",
36
39
  "tsdown": "^0.16.4",
37
- "typescript": "^5.9.3"
40
+ "typescript": "^5.9.3",
41
+ "vitest": "^4.1.0"
38
42
  }
39
43
  }