dst-rg 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.
Files changed (249) hide show
  1. package/.gitlab-ci.yml +43 -0
  2. package/.storybook/main.ts +15 -0
  3. package/.storybook/preview.ts +15 -0
  4. package/README.md +254 -0
  5. package/components.json +21 -0
  6. package/dist/Avatar.png +0 -0
  7. package/dist/assets/index-CCq7hmG3.js +186 -0
  8. package/dist/assets/index-Mg-hjQGu.css +1 -0
  9. package/dist/index.html +15 -0
  10. package/dist/test.png +0 -0
  11. package/dist/vite.svg +1 -0
  12. package/eslint.config.js +29 -0
  13. package/index.html +14 -0
  14. package/package.json +102 -0
  15. package/postcss.config.mjs +11 -0
  16. package/rollup.config.mjs +55 -0
  17. package/src/assets/react.svg +1 -0
  18. package/src/assets/style/animation.css +27 -0
  19. package/src/assets/style/box-shadow.css +25 -0
  20. package/src/assets/style/colors.css +402 -0
  21. package/src/assets/style/dark-theme.css +288 -0
  22. package/src/assets/style/font-size.css +14 -0
  23. package/src/assets/style/gradient.css +3 -0
  24. package/src/assets/style/index.css +12 -0
  25. package/src/assets/style/light-theme.css +148 -0
  26. package/src/assets/style/line-height.css +13 -0
  27. package/src/assets/style/max-width.css +5 -0
  28. package/src/assets/style/radius.css +13 -0
  29. package/src/assets/style/utility-colors.css +166 -0
  30. package/src/components/Accordion/_.stories.tsx +75 -0
  31. package/src/components/Accordion/_.test.tsx +77 -0
  32. package/src/components/Accordion/index.tsx +47 -0
  33. package/src/components/Accordion/type.ts +24 -0
  34. package/src/components/Avatar/_.stories.tsx +179 -0
  35. package/src/components/Avatar/_.style.ts +40 -0
  36. package/src/components/Avatar/_.test.tsx +150 -0
  37. package/src/components/Avatar/_.types.ts +66 -0
  38. package/src/components/Avatar/index.tsx +63 -0
  39. package/src/components/Badge/_.stories.tsx +75 -0
  40. package/src/components/Badge/_.style.ts +53 -0
  41. package/src/components/Badge/_.test.tsx +27 -0
  42. package/src/components/Badge/_.types.ts +11 -0
  43. package/src/components/Badge/index.tsx +42 -0
  44. package/src/components/Breadcrumbs/_.stories.tsx +95 -0
  45. package/src/components/Breadcrumbs/_.test.tsx +29 -0
  46. package/src/components/Breadcrumbs/_.type.ts +15 -0
  47. package/src/components/Breadcrumbs/index.tsx +103 -0
  48. package/src/components/Button/_.stories.tsx +85 -0
  49. package/src/components/Button/_.style.ts +56 -0
  50. package/src/components/Button/_.test.tsx +103 -0
  51. package/src/components/Button/_.types.ts +14 -0
  52. package/src/components/Button/index.tsx +70 -0
  53. package/src/components/Checkbox/_.stories.tsx +96 -0
  54. package/src/components/Checkbox/_.style.ts +24 -0
  55. package/src/components/Checkbox/_.test.tsx +85 -0
  56. package/src/components/Checkbox/_.types.ts +23 -0
  57. package/src/components/Checkbox/index.tsx +93 -0
  58. package/src/components/CheckboxGroup/PaymentCard/_.stories.tsx +104 -0
  59. package/src/components/CheckboxGroup/PaymentCard/_.style.ts +28 -0
  60. package/src/components/CheckboxGroup/PaymentCard/_.test.tsx +58 -0
  61. package/src/components/CheckboxGroup/PaymentCard/_.types.ts +28 -0
  62. package/src/components/CheckboxGroup/PaymentCard/index.tsx +71 -0
  63. package/src/components/CheckboxGroup/PlanCard/_.stories.tsx +165 -0
  64. package/src/components/CheckboxGroup/PlanCard/_.style.ts +32 -0
  65. package/src/components/CheckboxGroup/PlanCard/_.test.tsx +54 -0
  66. package/src/components/CheckboxGroup/PlanCard/_.types.ts +35 -0
  67. package/src/components/CheckboxGroup/PlanCard/index.tsx +53 -0
  68. package/src/components/CheckboxGroup/UserCard/_.stories.tsx +89 -0
  69. package/src/components/CheckboxGroup/UserCard/_.style.ts +42 -0
  70. package/src/components/CheckboxGroup/UserCard/_.test.tsx +66 -0
  71. package/src/components/CheckboxGroup/UserCard/_.types.ts +26 -0
  72. package/src/components/CheckboxGroup/UserCard/index.tsx +75 -0
  73. package/src/components/Dropdown/_.stories.tsx +180 -0
  74. package/src/components/Dropdown/_.style.ts +108 -0
  75. package/src/components/Dropdown/_.test.tsx +334 -0
  76. package/src/components/Dropdown/_.types.ts +12 -0
  77. package/src/components/Dropdown/index.tsx +130 -0
  78. package/src/components/FileUpload/_.stories.tsx +74 -0
  79. package/src/components/FileUpload/_.style.ts +0 -0
  80. package/src/components/FileUpload/_.test.tsx +222 -0
  81. package/src/components/FileUpload/_.types.ts +53 -0
  82. package/src/components/FileUpload/index.tsx +44 -0
  83. package/src/components/ImageMagnify/_.stories.tsx +226 -0
  84. package/src/components/ImageMagnify/_.style.ts +109 -0
  85. package/src/components/ImageMagnify/_.types.ts +44 -0
  86. package/src/components/ImageMagnify/index.tsx +204 -0
  87. package/src/components/Input/_.stories.tsx +177 -0
  88. package/src/components/Input/_.style.ts +79 -0
  89. package/src/components/Input/_.test.tsx +146 -0
  90. package/src/components/Input/_.types.ts +66 -0
  91. package/src/components/Input/index.tsx +231 -0
  92. package/src/components/InputTags/_.stories.tsx +51 -0
  93. package/src/components/InputTags/_.style.ts +28 -0
  94. package/src/components/InputTags/_.test.tsx +123 -0
  95. package/src/components/InputTags/_.types.ts +26 -0
  96. package/src/components/InputTags/index.tsx +140 -0
  97. package/src/components/Message/_.stories.tsx +79 -0
  98. package/src/components/Message/_.style.ts +87 -0
  99. package/src/components/Message/_.test.tsx +73 -0
  100. package/src/components/Message/_.types.ts +13 -0
  101. package/src/components/Message/index.tsx +57 -0
  102. package/src/components/Metric/_.stories.tsx +142 -0
  103. package/src/components/Metric/_.style.ts +14 -0
  104. package/src/components/Metric/_.test.tsx +166 -0
  105. package/src/components/Metric/_.types.ts +18 -0
  106. package/src/components/Metric/index.tsx +100 -0
  107. package/src/components/Modal/_.stories.tsx +93 -0
  108. package/src/components/Modal/_.style.ts +31 -0
  109. package/src/components/Modal/_.test.tsx +90 -0
  110. package/src/components/Modal/_.types.ts +14 -0
  111. package/src/components/Modal/index.tsx +82 -0
  112. package/src/components/Pagination/_.stories.tsx +118 -0
  113. package/src/components/Pagination/_.test.tsx +51 -0
  114. package/src/components/Pagination/index.tsx +256 -0
  115. package/src/components/Pagination/type.ts +48 -0
  116. package/src/components/PriceSlider/_.stories.tsx +107 -0
  117. package/src/components/PriceSlider/_.test.tsx +63 -0
  118. package/src/components/PriceSlider/_.type.tsx +19 -0
  119. package/src/components/PriceSlider/index.tsx +86 -0
  120. package/src/components/Progress/_.stories.tsx +93 -0
  121. package/src/components/Progress/_.style.ts +15 -0
  122. package/src/components/Progress/_.test.tsx +34 -0
  123. package/src/components/Progress/_.types.ts +17 -0
  124. package/src/components/Progress/index.tsx +173 -0
  125. package/src/components/Radio/_.stories.tsx +391 -0
  126. package/src/components/Radio/_.style.ts +33 -0
  127. package/src/components/Radio/_.test.tsx +77 -0
  128. package/src/components/Radio/_.types.ts +14 -0
  129. package/src/components/Radio/index.tsx +59 -0
  130. package/src/components/Select/_.stories.tsx +308 -0
  131. package/src/components/Select/_.style.ts +5 -0
  132. package/src/components/Select/_.types.ts +24 -0
  133. package/src/components/Select/index.tsx +172 -0
  134. package/src/components/Switch/_.stories.tsx +61 -0
  135. package/src/components/Switch/_.test.tsx +69 -0
  136. package/src/components/Switch/_.type.ts +12 -0
  137. package/src/components/Switch/index.tsx +70 -0
  138. package/src/components/Tabs/_.stories.tsx +508 -0
  139. package/src/components/Tabs/_.style.ts +63 -0
  140. package/src/components/Tabs/_.test.tsx +174 -0
  141. package/src/components/Tabs/_.type.ts +19 -0
  142. package/src/components/Tabs/index.tsx +35 -0
  143. package/src/components/Tag/_.stories.tsx +78 -0
  144. package/src/components/Tag/_.style.ts +71 -0
  145. package/src/components/Tag/_.test.tsx +44 -0
  146. package/src/components/Tag/_.types.ts +27 -0
  147. package/src/components/Tag/index.tsx +46 -0
  148. package/src/components/TextArea/_.stories.tsx +62 -0
  149. package/src/components/TextArea/_.style.ts +11 -0
  150. package/src/components/TextArea/_.test.tsx +43 -0
  151. package/src/components/TextArea/_.types.ts +29 -0
  152. package/src/components/TextArea/index.tsx +83 -0
  153. package/src/components/Toast/_.style.tsx +27 -0
  154. package/src/components/Toast/_.type.ts +30 -0
  155. package/src/components/Toast/_.utils.ts +23 -0
  156. package/src/components/Toast/container.tsx +171 -0
  157. package/src/components/Toast/index.tsx +29 -0
  158. package/src/components/Tooltip/_.stories.tsx +106 -0
  159. package/src/components/Tooltip/_.style.ts +27 -0
  160. package/src/components/Tooltip/_.test.tsx +54 -0
  161. package/src/components/Tooltip/_.types.ts +31 -0
  162. package/src/components/Tooltip/index.tsx +80 -0
  163. package/src/components/developers/AmirHossein.tsx +149 -0
  164. package/src/components/developers/Fardin.tsx +130 -0
  165. package/src/components/developers/Maryam.tsx +260 -0
  166. package/src/components/developers/Milad.tsx +431 -0
  167. package/src/components/developers/Rasoul.tsx +198 -0
  168. package/src/components/index.ts +28 -0
  169. package/src/components/ui/accordion.tsx +162 -0
  170. package/src/components/ui/avatars-component/avatar-description.tsx +30 -0
  171. package/src/components/ui/avatars-component/avatar-groups.tsx +68 -0
  172. package/src/components/ui/avatars-component/avatar-single.tsx +50 -0
  173. package/src/components/ui/card.tsx +92 -0
  174. package/src/components/ui/checkbox-group/plan-card/basic/_.test.tsx +66 -0
  175. package/src/components/ui/checkbox-group/plan-card/basic/index.tsx +70 -0
  176. package/src/components/ui/checkbox-group/plan-card/with-header/_.test.tsx +110 -0
  177. package/src/components/ui/checkbox-group/plan-card/with-header/header.test.tsx +96 -0
  178. package/src/components/ui/checkbox-group/plan-card/with-header/header.tsx +74 -0
  179. package/src/components/ui/checkbox-group/plan-card/with-header/index.tsx +65 -0
  180. package/src/components/ui/file-content/File-content.tsx +43 -0
  181. package/src/components/ui/file-uploader-components/file-uploader-box.tsx +76 -0
  182. package/src/components/ui/file-uploader-components/file-uploader-item.tsx +64 -0
  183. package/src/components/ui/icon-wrapper/_.test.tsx +60 -0
  184. package/src/components/ui/icon-wrapper/index.tsx +19 -0
  185. package/src/components/ui/input-component/input-label.tsx +11 -0
  186. package/src/components/ui/number.tsx +18 -0
  187. package/src/components/ui/pagination/card-minimal-center-align.tsx +96 -0
  188. package/src/components/ui/pagination/card-minimal-right-aligne.tsx +90 -0
  189. package/src/components/ui/pagination/default-pagination.tsx +128 -0
  190. package/src/components/ui/pagination/get-pagination-item.tsx +36 -0
  191. package/src/components/ui/pagination/pagination-card-button-group-aligned.tsx +94 -0
  192. package/src/components/ui/pagination/pagination-card-minimal-left-aligned.tsx +90 -0
  193. package/src/components/ui/pagination/pagination-content.tsx +15 -0
  194. package/src/components/ui/pagination/pagination-item.tsx +11 -0
  195. package/src/components/ui/pagination/pagination-link.tsx +42 -0
  196. package/src/components/ui/tab-components/tabs-content.tsx +15 -0
  197. package/src/components/ui/tab-components/tabs-list.tsx +27 -0
  198. package/src/components/ui/tab-components/tabs-trigger.tsx +25 -0
  199. package/src/components/ui/text-content-wrapper.tsx +36 -0
  200. package/src/hooks/useClickOutside.ts +23 -0
  201. package/src/icons/general/ArrowLeft.tsx +31 -0
  202. package/src/icons/general/ArrowRight.tsx +31 -0
  203. package/src/icons/general/activity-heart.tsx +31 -0
  204. package/src/icons/general/activity.tsx +31 -0
  205. package/src/icons/general/anchor.tsx +31 -0
  206. package/src/icons/general/archive.tsx +31 -0
  207. package/src/icons/general/arrow-left.tsx +25 -0
  208. package/src/icons/general/arrow-right.tsx +25 -0
  209. package/src/icons/general/asterisk-01.tsx +31 -0
  210. package/src/icons/general/asterisk-02.tsx +31 -0
  211. package/src/icons/general/at-sign.tsx +31 -0
  212. package/src/icons/general/attention-mark.tsx +43 -0
  213. package/src/icons/general/bookmark-add.tsx +31 -0
  214. package/src/icons/general/bookmark.tsx +31 -0
  215. package/src/icons/general/chevron-left.tsx +25 -0
  216. package/src/icons/general/chevron-right.tsx +25 -0
  217. package/src/icons/general/circle-minues.tsx +25 -0
  218. package/src/icons/general/circle-plus.tsx +25 -0
  219. package/src/icons/general/circle-question-mark.tsx +32 -0
  220. package/src/icons/general/circle.tsx +32 -0
  221. package/src/icons/general/copy.tsx +43 -0
  222. package/src/icons/general/email.tsx +32 -0
  223. package/src/icons/general/home.tsx +25 -0
  224. package/src/icons/general/layer.tsx +36 -0
  225. package/src/icons/general/leading.tsx +19 -0
  226. package/src/icons/general/master-card.tsx +37 -0
  227. package/src/icons/general/minus.tsx +36 -0
  228. package/src/icons/general/plus.tsx +19 -0
  229. package/src/icons/general/remove.tsx +32 -0
  230. package/src/icons/general/slash-divider.tsx +26 -0
  231. package/src/icons/general/tick-box.tsx +37 -0
  232. package/src/icons/general/trailing.tsx +19 -0
  233. package/src/icons/general/unkown-format.tsx +25 -0
  234. package/src/icons/general/visa-card.tsx +38 -0
  235. package/src/icons/general/x-close.tsx +35 -0
  236. package/src/icons/icons.type.ts +7 -0
  237. package/src/index.css +21 -0
  238. package/src/index.ts +3 -0
  239. package/src/lib/utils.ts +6 -0
  240. package/src/lib/zIndexUtils.ts +2 -0
  241. package/src/main.tsx +50 -0
  242. package/src/vite-env.d.ts +1 -0
  243. package/tests/setup.ts +8 -0
  244. package/tsconfig.app.json +31 -0
  245. package/tsconfig.json +7 -0
  246. package/tsconfig.node.json +24 -0
  247. package/tsconfig.rollup.json +12 -0
  248. package/vite.config.ts +20 -0
  249. package/vitest.config.ts +47 -0
@@ -0,0 +1,82 @@
1
+ import XClose from "@/icons/general/x-close";
2
+ import { getBackdropZIndex, getModalZIndex } from "@/lib/zIndexUtils";
3
+ import { useEffect, useRef } from "react";
4
+ import ReactDOM from "react-dom";
5
+ import { twMerge } from "tailwind-merge";
6
+ import { Button } from "../Button";
7
+ import { modalVariants } from "./_.style";
8
+ import { modalVariantProps } from "./_.types";
9
+
10
+ // Global variable to track modal depth
11
+ let modalDepth = 0;
12
+
13
+ export const Modal = ({
14
+ children,
15
+ closeHandler,
16
+ size = "md",
17
+ modalTitle,
18
+ overflow,
19
+ closeIconStroke = "#000",
20
+ position = "center",
21
+ icon,
22
+ headerIconDir = "flex-col",
23
+ showModal = false,
24
+ modalContentStyle,
25
+ showCloseIcon = true,
26
+ }: modalVariantProps) => {
27
+
28
+
29
+ const depthRef = useRef(++modalDepth);
30
+
31
+ // Disable scrolling when modal opens, enable when it closes
32
+ useEffect(() => {
33
+ if (showModal) {
34
+ document.body.style.overflow = "hidden"; // Prevent scrolling
35
+ }
36
+ return () => {
37
+ modalDepth--; // Decrease modal depth on unmount
38
+ document.body.style.overflow = ""; // Restore scrolling
39
+ };
40
+ }, [showModal]);
41
+
42
+ if (!showModal) return null;
43
+
44
+ return ReactDOM.createPortal(
45
+ <>
46
+ <button className="fixed left-0 top-0 h-full min-h-screen w-full cursor-default overflow-hidden bg-black/60 duration-0"
47
+ style={{ zIndex: getBackdropZIndex(depthRef.current) }}
48
+ onClick={closeHandler} />
49
+ <dialog
50
+ open
51
+ aria-label="modal-container"
52
+ className={twMerge(modalVariants({ size, overflow, position }), modalContentStyle)}
53
+ style={{ zIndex: getModalZIndex(depthRef.current) }}
54
+ >
55
+ <div
56
+ className={`${headerIconDir == "flex-col" ? "items-start" : "items-center"}
57
+ sticky top-0 z-10 flex justify-between gap-6 bg-white py-4`}
58
+ >
59
+ <div className={`flex gap-4 justify-start items-start ${headerIconDir}`}>
60
+ {icon && icon}
61
+ {modalTitle && (
62
+ <h2 id="modal-title" className="text-lg font-semibold">{modalTitle}</h2>
63
+ )}
64
+ </div>
65
+
66
+ {
67
+ showCloseIcon &&
68
+ <Button variant="tertiaryGray" size="sm" className="!p-0" onClick={closeHandler}>
69
+ <XClose width="16" height="16" stroke={closeIconStroke} />
70
+ </Button>
71
+ }
72
+
73
+ </div>
74
+ <div className="overflow-y-auto">
75
+ {children}
76
+ </div>
77
+ </dialog>
78
+ </>,
79
+ document.body,
80
+ );
81
+ };
82
+
@@ -0,0 +1,118 @@
1
+ import type { StoryObj } from "@storybook/react-vite";
2
+ import { Meta } from "@storybook/react-vite";
3
+ import { ArrowLeft, ArrowRight, ChevronsLeft, ChevronsRight } from "lucide-react";
4
+ import { Pagination } from ".";
5
+ import { PaginationProps } from "./type";
6
+
7
+ export default {
8
+ title: "Components/Pagination",
9
+ component: Pagination,
10
+ tags: ["autodocs"],
11
+ argTypes: {
12
+ hasText: {
13
+ control: { type: "boolean" },
14
+ },
15
+ showFirstLast: {
16
+ control: { type: "boolean" },
17
+ },
18
+ type: {
19
+ control: { type: "radio" },
20
+ options: [
21
+ "default",
22
+ "card-minimal-right-aligned",
23
+ "card-minimal-left-aligned",
24
+ "card-minimal-center-aligned",
25
+ "card-button-group-right-aligned",
26
+ "card-button-group-left-aligned",
27
+ "card-button-group-center-aligned",
28
+ ],
29
+ },
30
+ dir: {
31
+ control: { type: "radio" },
32
+ options: ["rtl", "ltr"],
33
+ },
34
+ onPageChange: { action: "page changed" },
35
+ },
36
+ args: {
37
+ currentPage: 3,
38
+ totalPages: 10,
39
+ type: "default",
40
+ dir: "ltr",
41
+ },
42
+ } as Meta<typeof Pagination>;
43
+
44
+ type Story = StoryObj<PaginationProps & { type?: string }>;
45
+
46
+ export const Default: Story = {};
47
+
48
+ export const CardMinimalRightAligned: Story = {
49
+ args: {
50
+ type: "card-minimal-right-aligned",
51
+ },
52
+ };
53
+
54
+ export const CardMinimalLeftAligned: Story = {
55
+ args: {
56
+ type: "card-minimal-left-aligned",
57
+ },
58
+ };
59
+
60
+ export const CardMinimalCenterAligned: Story = {
61
+ args: {
62
+ type: "card-minimal-center-aligned",
63
+ },
64
+ };
65
+
66
+ export const ButtonGroupRightAligned: Story = {
67
+ args: {
68
+ type: "card-button-group-right-aligned",
69
+ hasText: false,
70
+ },
71
+ };
72
+
73
+ export const ButtonGroupRightAlignedWithText: Story = {
74
+ args: {
75
+ type: "card-button-group-right-aligned",
76
+ hasText: true,
77
+ },
78
+ };
79
+
80
+ export const ButtonGroupLeftAligned: Story = {
81
+ args: {
82
+ type: "card-button-group-left-aligned",
83
+ hasText: false,
84
+ },
85
+ };
86
+
87
+ export const ButtonGroupCenterAligned: Story = {
88
+ args: {
89
+ type: "card-button-group-center-aligned",
90
+ hasText: false,
91
+ },
92
+ };
93
+
94
+ export const CustomIcons: Story = {
95
+ args: {
96
+ type: "default",
97
+ prevIcon: <ArrowLeft className="w-4 h-4" />,
98
+ nextIcon: <ArrowRight className="w-4 h-4" />,
99
+ firstIcon: <ChevronsLeft className="w-4 h-4" />,
100
+ lastIcon: <ChevronsRight className="w-4 h-4" />,
101
+ },
102
+ };
103
+
104
+ export const WithoutFirstLastButtons: Story = {
105
+ args: {
106
+ type: "default",
107
+ showFirstLast: false,
108
+ },
109
+ };
110
+
111
+ export const CustomIconsWithoutFirstLast: Story = {
112
+ args: {
113
+ type: "default",
114
+ prevIcon: <ArrowLeft className="w-4 h-4" />,
115
+ nextIcon: <ArrowRight className="w-4 h-4" />,
116
+ showFirstLast: false,
117
+ },
118
+ };
@@ -0,0 +1,51 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { Pagination } from ".";
4
+
5
+ describe("Pagination Component", () => {
6
+ const defaultProps = {
7
+ currentPage: 2,
8
+ totalPages: 10,
9
+ onPageChange: vi.fn(),
10
+ };
11
+
12
+ it("renders default pagination", () => {
13
+ render(<Pagination {...defaultProps} />);
14
+ expect(screen.getByText("بعدی")).toBeInTheDocument();
15
+ expect(screen.getByText("قبلی")).toBeInTheDocument();
16
+ });
17
+
18
+ it("renders card-minimal-right-aligned pagination", () => {
19
+ render(<Pagination {...defaultProps} type="card-minimal-right-aligned" />);
20
+ expect(screen.getByText("بعدی")).toBeInTheDocument();
21
+ });
22
+
23
+ it("renders card-minimal-left-aligned pagination", () => {
24
+ render(<Pagination {...defaultProps} type="card-minimal-left-aligned" />);
25
+ expect(screen.getByText("بعدی")).toBeInTheDocument();
26
+ });
27
+
28
+ it("renders card-button-group-center-aligned pagination with page buttons", () => {
29
+ render(
30
+ <Pagination {...defaultProps} type="card-button-group-center-aligned" />
31
+ );
32
+ expect(screen.getByText("بعدی")).toBeInTheDocument();
33
+ expect(screen.getByText("قبلی")).toBeInTheDocument();
34
+ expect(screen.getByText("2")).toBeInTheDocument();
35
+ });
36
+
37
+ it("calls onPageChange when a page button is clicked", () => {
38
+ render(
39
+ <Pagination {...defaultProps} type="card-button-group-center-aligned" />
40
+ );
41
+ const pageButton = screen.getByText("2");
42
+ pageButton.click();
43
+ expect(defaultProps.onPageChange).toHaveBeenCalledWith(2);
44
+ });
45
+
46
+ it("renders with English labels when direction is ltr", () => {
47
+ render(<Pagination {...defaultProps} dir="ltr" />);
48
+ expect(screen.getByText("Next")).toBeInTheDocument();
49
+ expect(screen.getByText("Previous")).toBeInTheDocument();
50
+ });
51
+ });
@@ -0,0 +1,256 @@
1
+ import { cn } from "@/lib/utils";
2
+ import { twMerge } from "tailwind-merge";
3
+ import PaginationCardMinimalCenter from "../ui/pagination/card-minimal-center-align";
4
+ import PaginationCardMinimalRightAligned from "../ui/pagination/card-minimal-right-aligne";
5
+ import DefaultPagination from "../ui/pagination/default-pagination";
6
+ import PaginationCardButtonGroupAligned from "../ui/pagination/pagination-card-button-group-aligned";
7
+ import PaginationCardMinimalLeftAligned from "../ui/pagination/pagination-card-minimal-left-aligned";
8
+ import { PaginationProps } from "./type";
9
+
10
+ export const Pagination = ({
11
+ currentPage,
12
+ totalPages,
13
+ onPageChange,
14
+ type = "default",
15
+ dir = "rtl",
16
+ className,
17
+ activeClassName,
18
+ previousClassName,
19
+ nextClassName,
20
+ prevIcon,
21
+ nextIcon,
22
+ firstIcon,
23
+ lastIcon,
24
+ showFirstLast = true,
25
+ labels,
26
+ hasText = true,
27
+ ...props
28
+ }: PaginationProps & { type?: string }) => {
29
+ const isRtl = dir === "rtl";
30
+
31
+ const defaultLabels = {
32
+ next: isRtl ? "بعدی" : "Next",
33
+ prev: isRtl ? "قبلی" : "Previous",
34
+ first: isRtl ? "اولین" : "First",
35
+ last: isRtl ? "آخرین" : "Last",
36
+ page: isRtl ? "صفحه" : "page",
37
+ of: isRtl ? "از" : "of",
38
+ };
39
+ const mergedLabels = { ...defaultLabels, ...labels };
40
+
41
+ if (totalPages <= 1) {
42
+ return null;
43
+ }
44
+
45
+ const renderPageNumbers = () => {
46
+ const isMobile = typeof window !== "undefined" && window.innerWidth < 768;
47
+ const pages: (number | "...")[] = [];
48
+
49
+ if (isMobile) {
50
+ const mobileSet = new Set<number>();
51
+ mobileSet.add(1);
52
+ mobileSet.add(2);
53
+ mobileSet.add(totalPages - 1);
54
+ mobileSet.add(totalPages);
55
+
56
+ if (
57
+ currentPage !== 1 &&
58
+ currentPage !== 2 &&
59
+ currentPage !== totalPages - 1 &&
60
+ currentPage !== totalPages
61
+ ) {
62
+ mobileSet.add(currentPage);
63
+ }
64
+
65
+ const sorted = Array.from(mobileSet).sort((a, b) => a - b);
66
+ for (let i = 0; i < sorted.length; i++) {
67
+ pages.push(sorted[i]);
68
+ if (i < sorted.length - 1 && sorted[i + 1] !== sorted[i] + 1) {
69
+ pages.push("...");
70
+ }
71
+ }
72
+ } else {
73
+ if (totalPages <= 6) {
74
+ for (let i = 1; i <= totalPages; i++) pages.push(i);
75
+ } else {
76
+ const showLeftDots = currentPage > 3;
77
+ const showRightDots = currentPage < totalPages - 2;
78
+
79
+ if (!showLeftDots) {
80
+ for (let i = 1; i <= 3; i++) pages.push(i);
81
+ pages.push("...", totalPages - 2, totalPages - 1, totalPages);
82
+ } else if (!showRightDots) {
83
+ pages.push(1, 2, 3, "...");
84
+ for (let i = totalPages - 2; i <= totalPages; i++) pages.push(i);
85
+ } else {
86
+ pages.push(
87
+ 1,
88
+ "...",
89
+ currentPage - 1,
90
+ currentPage,
91
+ currentPage + 1,
92
+ "...",
93
+ totalPages
94
+ );
95
+ }
96
+ }
97
+ }
98
+
99
+ const finalPages = isRtl ? [...pages].reverse() : pages;
100
+
101
+ return (
102
+ <div className={cn("flex h-full", isRtl && "flex-row-reverse")}>
103
+ {finalPages.map((page, index) =>
104
+ page === "..." ? (
105
+ <span
106
+ key={`dots-${index}`}
107
+ className="md:px-3 md:py-1 p-2 text-gray-400 text-sm"
108
+ >
109
+ ...
110
+ </span>
111
+ ) : (
112
+ <button
113
+ key={page}
114
+ onClick={() => onPageChange?.(page)}
115
+ className={cn(
116
+ "px-3 py-1 text-sm border border-gray-200",
117
+ currentPage === page ? activeClassName : className
118
+ )}
119
+ >
120
+ {page}
121
+ </button>
122
+ )
123
+ )}
124
+ </div>
125
+ );
126
+ };
127
+
128
+ if (type === "card-minimal-right-aligned") {
129
+ return (
130
+ <PaginationCardMinimalRightAligned
131
+ currentPage={currentPage}
132
+ totalPages={totalPages}
133
+ onPageChange={onPageChange}
134
+ dir={dir}
135
+ labels={mergedLabels}
136
+ previousClassName={previousClassName}
137
+ nextClassName={nextClassName}
138
+ className={className}
139
+ {...props}
140
+ />
141
+ );
142
+ }
143
+ if (type === "card-minimal-left-aligned") {
144
+ return (
145
+ <PaginationCardMinimalLeftAligned
146
+ currentPage={currentPage}
147
+ totalPages={totalPages}
148
+ onPageChange={onPageChange}
149
+ dir={dir}
150
+ labels={mergedLabels}
151
+ previousClassName={previousClassName}
152
+ nextClassName={nextClassName}
153
+ className={className}
154
+ {...props}
155
+ />
156
+ );
157
+ }
158
+
159
+ if (type === "card-minimal-center-aligned") {
160
+ return (
161
+ <PaginationCardMinimalCenter
162
+ currentPage={currentPage}
163
+ totalPages={totalPages}
164
+ onPageChange={onPageChange}
165
+ dir={dir}
166
+ labels={mergedLabels}
167
+ previousClassName={previousClassName}
168
+ nextClassName={nextClassName}
169
+ className={className}
170
+ prevIcon={prevIcon}
171
+ nextIcon={nextIcon}
172
+ {...props}
173
+ />
174
+ );
175
+ }
176
+
177
+ if (type === "card-button-group-right-aligned") {
178
+ return (
179
+ <PaginationCardButtonGroupAligned
180
+ currentPage={currentPage}
181
+ totalPages={totalPages}
182
+ onPageChange={onPageChange}
183
+ dir={dir}
184
+ labels={mergedLabels}
185
+ renderPageNumbers={renderPageNumbers}
186
+ previousClassName={previousClassName}
187
+ nextClassName={nextClassName}
188
+ justifyClassName={twMerge(className, 'justify-end')}
189
+ prevIcon={prevIcon}
190
+ nextIcon={nextIcon}
191
+ hasText={hasText}
192
+ {...props}
193
+ />
194
+ );
195
+ }
196
+
197
+ if (type === "card-button-group-left-aligned") {
198
+ return (
199
+ <PaginationCardButtonGroupAligned
200
+ currentPage={currentPage}
201
+ totalPages={totalPages}
202
+ onPageChange={onPageChange}
203
+ dir={dir}
204
+ labels={mergedLabels}
205
+ renderPageNumbers={renderPageNumbers}
206
+ previousClassName={previousClassName}
207
+ nextClassName={nextClassName}
208
+ justifyClassName={twMerge(className, 'justify-start')}
209
+ prevIcon={prevIcon}
210
+ nextIcon={nextIcon}
211
+ hasText={hasText}
212
+ {...props}
213
+ />
214
+ );
215
+ }
216
+
217
+ if (type === "card-button-group-center-aligned") {
218
+ return (
219
+ <PaginationCardButtonGroupAligned
220
+ currentPage={currentPage}
221
+ totalPages={totalPages}
222
+ onPageChange={onPageChange}
223
+ dir={dir}
224
+ labels={mergedLabels}
225
+ renderPageNumbers={renderPageNumbers}
226
+ previousClassName={previousClassName}
227
+ nextClassName={nextClassName}
228
+ justifyClassName={twMerge(className, 'justify-center')}
229
+ prevIcon={prevIcon}
230
+ nextIcon={nextIcon}
231
+ hasText={hasText}
232
+ {...props}
233
+ />
234
+ );
235
+ }
236
+
237
+ return (
238
+ <DefaultPagination
239
+ activeClassName={activeClassName}
240
+ currentPage={currentPage}
241
+ totalPages={totalPages}
242
+ onPageChange={onPageChange}
243
+ dir={dir}
244
+ labels={mergedLabels}
245
+ previousClassName={previousClassName}
246
+ nextClassName={nextClassName}
247
+ className={className}
248
+ prevIcon={prevIcon}
249
+ nextIcon={nextIcon}
250
+ firstIcon={firstIcon}
251
+ lastIcon={lastIcon}
252
+ showFirstLast={showFirstLast}
253
+ hasText={hasText}
254
+ />
255
+ );
256
+ };
@@ -0,0 +1,48 @@
1
+ import { ReactNode } from "react";
2
+
3
+ export type PaginationType =
4
+ | "default"
5
+ | "card-minimal-right-aligned"
6
+ | "card-minimal-left-aligned"
7
+ | "card-minimal-center-aligned"
8
+ | "card-button-group-right-aligned"
9
+ | "card-button-group-left-aligned"
10
+ | "card-button-group-center-aligned";
11
+
12
+ export type PaginationProps = {
13
+ currentPage: number;
14
+ totalPages: number;
15
+ onPageChange?: (page: number) => void;
16
+ type?: PaginationType;
17
+ className?: string;
18
+ justifyClassName?: string;
19
+ activeClassName?: string;
20
+ previousClassName?: string;
21
+ nextClassName?: string;
22
+ prevIcon?: ReactNode;
23
+ nextIcon?: ReactNode;
24
+ firstIcon?: ReactNode;
25
+ lastIcon?: ReactNode;
26
+ showFirstLast?: boolean;
27
+ dir?: string;
28
+ hasText?: boolean;
29
+ labels?: {
30
+ next: string;
31
+ prev: string;
32
+ first: string;
33
+ last: string;
34
+ page: string;
35
+ of: string;
36
+ };
37
+ } & React.ComponentProps<"nav">;
38
+
39
+ export type PaginationLinkProps = {
40
+ isActive?: boolean;
41
+ activeClassName?: string;
42
+ disabled?: boolean;
43
+ size?: "icon" | "default";
44
+ } & React.ComponentProps<"a">;
45
+
46
+ export interface PaginationCardButtonGroupAlignedProps extends PaginationProps {
47
+ renderPageNumbers: () => React.ReactNode;
48
+ }
@@ -0,0 +1,107 @@
1
+ import { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { PriceSlider } from ".";
3
+ import { SliderProps } from "./_.type";
4
+
5
+ export default {
6
+ title: "Components/PriceSlider",
7
+ component: PriceSlider,
8
+ tags: ["autodocs"],
9
+ argTypes: {
10
+ defaultValue: {
11
+ control: { type: "object" },
12
+ description: "The initial range of values for the slider",
13
+ },
14
+ className: {
15
+ control: { type: "text" },
16
+ description: "Custom className for the slider wrapper",
17
+ },
18
+ trackClassName: {
19
+ control: { type: "text" },
20
+ description: "Custom className for the track of the slider",
21
+ },
22
+ rangeClassName: {
23
+ control: { type: "text" },
24
+ description: "Custom className for the range of the slider",
25
+ },
26
+ thumbClassName: {
27
+ control: { type: "text" },
28
+ description: "Custom className for the thumbs (sliders) on the range",
29
+ },
30
+ showValue: {
31
+ control: { type: "boolean" },
32
+ description: "Show the current value next to the thumb",
33
+ },
34
+ valueClassName: {
35
+ control: { type: "text" },
36
+ description: "Custom className for the value label",
37
+ },
38
+ isPercentage: {
39
+ control: { type: "boolean" },
40
+ description: "Display the value as a percentage",
41
+ },
42
+ valuePosition: {
43
+ control: { type: "radio" },
44
+ options: ["top", "bottom"],
45
+ description: "Position of the value label relative to the thumb",
46
+ },
47
+ dir: {
48
+ control: { type: "radio" },
49
+ options: ["ltr", "rtl"],
50
+ description: "Direction of the slider, Left-to-Right or Right-to-Left",
51
+ },
52
+ },
53
+ args: {
54
+ min: 0,
55
+ max: 100,
56
+ defaultValue: [0, 100],
57
+ className: "",
58
+ trackClassName: "",
59
+ rangeClassName: "",
60
+ thumbClassName: "",
61
+ showValue: false,
62
+ valueClassName: "",
63
+ isPercentage: false,
64
+ valuePosition: "bottom",
65
+ dir: "ltr",
66
+ },
67
+ } as Meta<typeof PriceSlider>;
68
+
69
+ type Story = StoryObj<SliderProps>;
70
+
71
+ export const Default: Story = {};
72
+
73
+ export const WithPercentageValue: Story = {
74
+ args: {
75
+ min: 0,
76
+ max: 100,
77
+ defaultValue: [0, 50],
78
+ showValue: true,
79
+ isPercentage: true,
80
+ },
81
+ };
82
+
83
+ export const WithCustomClasses: Story = {
84
+ args: {
85
+ min: 0,
86
+ max: 10000,
87
+ defaultValue: [2000, 8000],
88
+ trackClassName: "bg-gray-300",
89
+ rangeClassName: "bg-green-500",
90
+ thumbClassName: "bg-blue-500",
91
+ showValue: true,
92
+ valueClassName: "text-red-500",
93
+ },
94
+ };
95
+
96
+ export const RTLDirection: Story = {
97
+ args: {
98
+ min: 0,
99
+ max: 100,
100
+ defaultValue: [10, 90],
101
+ dir: "rtl",
102
+ showValue: true,
103
+ isPercentage: true,
104
+ },
105
+ };
106
+
107
+