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,42 @@
1
+ import XClose from "@/icons/general/x-close";
2
+ import { cn } from "@/lib/utils";
3
+ import { BadgeProps } from "./_.types";
4
+ import { badgeVariants } from "./_.style";
5
+
6
+ function Badge({
7
+ className,
8
+ size = "sm",
9
+ type = "pillColor",
10
+ color = "blue",
11
+ dir = "rtl",
12
+ icon,
13
+ closable = false,
14
+ onClose,
15
+ children,
16
+ ...props
17
+ }: Readonly<BadgeProps>) {
18
+ return (
19
+ <div
20
+ className={cn(
21
+ "w-fit",
22
+ badgeVariants({ size, type, dir, color }),
23
+ type === "badgeModern" &&
24
+ "bg-transparent border-primary text-secondary",
25
+ className
26
+ )}
27
+ {...props}
28
+ >
29
+ {icon}
30
+ {/* Avatar or any other custom icon/image can be passed via children */}
31
+ {children}
32
+ {closable && (
33
+ <button onClick={onClose} className="ml-1">
34
+ <XClose className="w-3 h-3 stroke-current" />
35
+ </button>
36
+ )}
37
+ </div>
38
+ );
39
+ }
40
+
41
+ export { Badge };
42
+
@@ -0,0 +1,95 @@
1
+ import type { StoryObj } from "@storybook/react-vite";
2
+ import { Breadcrumb } from ".";
3
+ import { BreadcrumbProps } from "./_.type";
4
+
5
+ export default {
6
+ title: "Components/Breadcrumb",
7
+ component: Breadcrumb,
8
+ tags: ["autodocs"],
9
+ argTypes: {
10
+ type: {
11
+ control: { type: "radio" },
12
+ options: ["chevron", "slash"],
13
+ },
14
+ dir: {
15
+ control: { type: "radio" },
16
+ options: ["ltr", "rtl"],
17
+ },
18
+ BreadcrumbProps: {
19
+ control: { type: "array" },
20
+ },
21
+ navClassName: {
22
+ control: { type: "text" },
23
+ },
24
+ listClassName: {
25
+ control: { type: "text" },
26
+ },
27
+ listItemClassName: {
28
+ control: { type: "text" },
29
+ },
30
+ linkClassName: {
31
+ control: { type: "text" },
32
+ },
33
+ separatorClassName: {
34
+ control: { type: "text" },
35
+ },
36
+ },
37
+ args: {
38
+ type: "chevron",
39
+ dir: "ltr",
40
+ BreadcrumbProps: [
41
+ { title: "Category", link: "/category" },
42
+ { title: "Subcategory", link: "/subcategory" },
43
+ ],
44
+ navClassName: "",
45
+ listClassName: "",
46
+ listItemClassName: "",
47
+ linkClassName: "text-blue-500",
48
+ separatorClassName: "ms-2",
49
+ },
50
+ };
51
+
52
+ type Story = StoryObj<BreadcrumbProps>;
53
+
54
+ export const WithSlashSeparator: Story = {
55
+ args: {
56
+ type: "slash",
57
+ breadCrumbLink: [
58
+ { title: "Home", link: "/" },
59
+ { title: "Category", link: "/category" },
60
+ { title: "Product", link: "/product" },
61
+ ],
62
+ },
63
+ };
64
+
65
+ export const RTL: Story = {
66
+ args: {
67
+ dir: "rtl",
68
+ breadCrumbLink: [
69
+ { title: "خانه", link: "/" },
70
+ { title: "دسته بندی", link: "/category" },
71
+ { title: "محصولات", link: "/product" },
72
+ ],
73
+ },
74
+ };
75
+
76
+ export const CustomClassNames: Story = {
77
+ args: {
78
+ breadCrumbLink: [
79
+ { title: "Home", link: "/" },
80
+ { title: "Category", link: "/category" },
81
+ { title: "Subcategory", link: "/subcategory" },
82
+ ],
83
+ navClassName: "bg-gray-100 p-4",
84
+ listClassName: "list-none flex gap-x-2",
85
+ listItemClassName: "text-lg font-semibold",
86
+ linkClassName: "text-blue-500 hover:underline",
87
+ separatorClassName: "text-gray-500",
88
+ },
89
+ };
90
+
91
+ export const WithEmptyBreadcrumb: Story = {
92
+ args: {
93
+ breadCrumbLink: [],
94
+ },
95
+ };
@@ -0,0 +1,29 @@
1
+
2
+ import { render, screen } from "@testing-library/react";
3
+ import { describe, expect, it, vi } from "vitest";
4
+ import { Breadcrumb } from ".";
5
+ import { BreadcrumbTypes } from "./_.type";
6
+
7
+ describe("Breadcrumb Component", () => {
8
+ const breadCrumbLink: BreadcrumbTypes[] = [
9
+ { title: "Home", link: "/" },
10
+ { title: "Category", link: "/category" },
11
+ { title: "Product", link: "/category/product" },
12
+ ];
13
+
14
+
15
+ it("renders the correct active breadcrumb based on location", () => {
16
+ vi.stubGlobal("location", { pathname: "/category/product" });
17
+
18
+ render(<Breadcrumb breadCrumbLink={breadCrumbLink} />);
19
+
20
+ const activeBreadcrumb = screen.getByText(/Product/i);
21
+ expect(activeBreadcrumb).toHaveClass("text-rtext-brand-tertiary-600");
22
+ });
23
+
24
+ it("should handle RTL direction correctly", () => {
25
+ render(<Breadcrumb breadCrumbLink={breadCrumbLink} dir="rtl" />);
26
+
27
+ expect(screen.getByRole("navigation")).toHaveAttribute("dir", "rtl");
28
+ });
29
+ });
@@ -0,0 +1,15 @@
1
+ export interface BreadcrumbTypes {
2
+ link?: string;
3
+ title: string;
4
+ }
5
+
6
+ export interface BreadcrumbProps {
7
+ breadCrumbLink: BreadcrumbTypes[];
8
+ type?: "slash" | "chevron";
9
+ dir?: "rtl" | "ltr";
10
+ navClassName?: string;
11
+ listClassName?: string;
12
+ listItemClassName?: string;
13
+ linkClassName?: string;
14
+ separatorClassName?: string;
15
+ }
@@ -0,0 +1,103 @@
1
+ import ChevronLeft from "@/icons/general/chevron-left";
2
+ import ChevronRight from "@/icons/general/chevron-right";
3
+ import Home from "@/icons/general/home";
4
+ import SlashDivider from "@/icons/general/slash-divider";
5
+ import { cn } from "@/lib/utils";
6
+ import { useEffect, useState } from "react";
7
+ import { BreadcrumbProps } from "./_.type";
8
+
9
+ export const Breadcrumb: React.FC<BreadcrumbProps> = ({
10
+ breadCrumbLink,
11
+ type = "chevron",
12
+ dir,
13
+ navClassName,
14
+ listClassName,
15
+ listItemClassName = '',
16
+ linkClassName,
17
+ separatorClassName,
18
+ }) => {
19
+ const [currentPath, setCurrentPath] = useState<string>("");
20
+
21
+ useEffect(() => {
22
+ if (typeof window !== "undefined") {
23
+ setCurrentPath(window.location.pathname);
24
+ }
25
+ }, []);
26
+
27
+ const direction: "ltr" | "rtl" =
28
+ dir ?? (document?.body?.getAttribute("dir") === "rtl" ? "rtl" : "ltr");
29
+ const renderSeparator = () => {
30
+ switch (type) {
31
+ case "chevron":
32
+ return direction === "rtl" ? <ChevronLeft /> : <ChevronRight />;
33
+ case "slash":
34
+ return <SlashDivider />;
35
+ default:
36
+ return <ChevronRight />;
37
+ }
38
+ };
39
+
40
+ const allItems = [
41
+ {
42
+ title: <Home />,
43
+ link: "/",
44
+ },
45
+ ...breadCrumbLink,
46
+ ];
47
+
48
+ const activeIndex = allItems.findIndex((item) => item.link === currentPath);
49
+
50
+ const visibleItems =
51
+ activeIndex >= 0 ? allItems.slice(0, activeIndex + 1) : allItems;
52
+
53
+ return (
54
+ <nav
55
+ className={`w-full overflow-x-auto scrollbar-thin scrollbar-thumb-gray-400 ${navClassName ?? ""
56
+ }`}
57
+ dir={direction}
58
+ >
59
+ <ol
60
+ className={`flex flex-nowrap items-center gap-x-1 lg:gap-x-2 w-max py-2 rounded-md ${listClassName ?? ""
61
+ }`}
62
+ >
63
+ {" "}
64
+ {visibleItems.map((breadcrumb, index) => {
65
+ const href = breadcrumb.link ?? "";
66
+ const showSeparator = index !== visibleItems.length - 1;
67
+ const isActive = currentPath === href;
68
+
69
+ return (
70
+ <li
71
+ key={`${href}-${index}`}
72
+ className={cn(
73
+ `flex items-center text-sm font-normal leading-normal transition-colors duration-300 `,
74
+ listItemClassName
75
+ )}
76
+ >
77
+ {breadcrumb.link ? (
78
+ <a
79
+ href={href}
80
+ className={`${linkClassName ??
81
+ "cursor-pointer transition-colors duration-300"
82
+ } ${isActive
83
+ ? "text-rtext-brand-tertiary-600"
84
+ : "text-rtext-placeholder"
85
+ }`}
86
+ >
87
+ {breadcrumb.title}
88
+ </a>
89
+ ) : (
90
+ <span className="text-gray-500">{breadcrumb.title}</span>
91
+ )}
92
+ {showSeparator && (
93
+ <span className={`${separatorClassName ?? "ms-2"}`}>
94
+ {renderSeparator()}
95
+ </span>
96
+ )}
97
+ </li>
98
+ );
99
+ })}
100
+ </ol>
101
+ </nav>
102
+ );
103
+ };
@@ -0,0 +1,85 @@
1
+ import Plus from "@/icons/general/plus";
2
+ import type { StoryObj } from "@storybook/react-vite";
3
+ import { Meta } from "@storybook/react-vite";
4
+ import { ArrowLeft } from "lucide-react";
5
+ import { Button } from ".";
6
+ import { buttonVariants } from "./_.style";
7
+ import { ButtonProps } from "./_.types";
8
+
9
+ export default {
10
+ title: "Components/Button",
11
+ component: Button,
12
+ tags: ["autodocs"],
13
+ argTypes: {
14
+ size: {
15
+ control: { type: "radio" },
16
+ options: ["sm", "md", "lg", "xl", "2xl"],
17
+ },
18
+ variant: {
19
+ control: { type: "radio" }, // Enables a dropdown in Storybook UI
20
+ options: ["primary", "secondaryGray", "secondaryColor", "tertiaryGray", "tertiaryColor", "linkGray", "linkColor"],
21
+ },
22
+ dir: {
23
+ control: { type: "radio" },
24
+ options: ["ltr", "rtl"],
25
+ },
26
+ fullWidth: {
27
+ control: { type: "boolean" },
28
+ },
29
+ disabled: {
30
+ control: { type: "boolean" },
31
+ },
32
+ onClick: {
33
+ control: { action: "close" },
34
+ }
35
+ },
36
+ args: {
37
+ size: "md",
38
+ dir: "rtl",
39
+ variant: "primary",
40
+ children: "Badge Label",
41
+ },
42
+ } as Meta<typeof buttonVariants>;
43
+
44
+ type Story = StoryObj<ButtonProps>;
45
+
46
+ export const Default: Story = {};
47
+
48
+ export const FullWidth = {
49
+ args: {
50
+ fullWidth: true,
51
+ children: "Badge Label",
52
+ },
53
+ };
54
+
55
+ export const LeftIcon = {
56
+ args: {
57
+ leftIcon: <Plus width="16" height="16" stroke="white" />,
58
+ children: "Badge Label",
59
+ },
60
+ }
61
+
62
+ export const RightIcon = {
63
+ args: {
64
+ rightIcon: <ArrowLeft stroke="blue" width={16} />,
65
+ children: "Badge Label",
66
+ size: "sm",
67
+ variant: "secondaryColor"
68
+ },
69
+ }
70
+
71
+ export const Disabled = {
72
+ args: {
73
+ disabled: true,
74
+ children: "Badge Label",
75
+ variant: "primary"
76
+ },
77
+ }
78
+
79
+ export const Loading = {
80
+ args: {
81
+ loading: true,
82
+ children: "Badge Label",
83
+ variant: "secondaryColor"
84
+ },
85
+ }
@@ -0,0 +1,56 @@
1
+ import { cva } from "class-variance-authority";
2
+
3
+ const baseStyles =
4
+ "inline-flex items-center justify-center font-medium focus:outline-none transition-all w-fit rounded-md border transition-all duration-300 cursor-pointer relative";
5
+
6
+ const variantStyle = {
7
+ primary:
8
+ "border-gradient-border bg-rbutton-primary-bg hover:bg-rbutton-primary-bg-hover focus:bg-rbutton-primary-bg shadow-xs-skeuomorphic focus:shadow-focus-ring-shadow-xs-skeuomorphic text-rbutton-primary-fg has-[>svg]:text-rbutton-primary-fg disabled:bg-rbg-disabled disabled:border-rborder-disabled-subtle disabled:text-rfg-disabled has-[>svg]:disabled:text-rfg-disabled disabled:shadow-xs",
9
+ secondaryGray:
10
+ "text-rbutton-secondary-fg has-[>svg]:stroke-rbutton-secondary-fg border-gradient-border bg-rbutton-secondary-bg hover:bg-rbutton-secondary-bg-hover shadow-xs-skeuomorphic disabled:bg-rbg-primary disabled:border-rborder-disabled-subtle disabled:text-rfg-disabled has-[>svg]:disabled:text-rfg-disabled disabled:shadow-xs",
11
+ secondaryColor:
12
+ "text-rbutton-secondary-color-fg bg-rbutton-secondary-color-bg border-rbutton-secondary-color-border hover:text-rbutton-secondary-color-fg-hover hover:bg-rbutton-secondary-color-bg-hover border-rbutton-secondary-color-border-hover disabled:bg-rbg-primary disabled:border-rborder-disabled-subtle disabled:text-rfg-disabled disabled:shadow-xs",
13
+ tertiaryGray:
14
+ "border-0 text-rbutton-tertiary-fg hover:text-rbutton-tertiary-fg-hover hover:bg-rbutton-tertiary-bg-hover disabled:text-rfg-disabled",
15
+ tertiaryColor:
16
+ "border-0 text-rbutton-tertiary-color-fg hover:text-rbutton-tertiary-color-fg-hover hover:bg-rbutton-tertiary-color-bg-hover disabled:text-rfg-disabled",
17
+ linkGray:
18
+ "border-0 text-rbutton-tertiary-fg hover:text-rbutton-tertiary-fg-hover disabled:text-rfg-disabled !p-0",
19
+ linkColor:
20
+ "border-0 text-rbutton-tertiary-color-fg hover:text-rbutton-tertiary-color-fg-hover disabled:text-rfg-disabled !p-0",
21
+ };
22
+
23
+ const sizeStyle = {
24
+ sm: "text-sm px-3 py-2 gap-1",
25
+ md: "text-base px-3.5 py-2.5 gap-1",
26
+ lg: "text-lg px-4 py-2.5 gap-1.5",
27
+ xl: "text-lg px-4.5 py-3 gap-1.5",
28
+ "2xl": "rounded-lg text-lg px-5.5 py-4 gap-2",
29
+ };
30
+
31
+ export const buttonVariants = cva(
32
+ baseStyles,
33
+ {
34
+ variants: {
35
+ loading: {
36
+ true: "pointer-events-none",
37
+ },
38
+ size: sizeStyle,
39
+ fullWidth: {
40
+ true: "w-full",
41
+ },
42
+ disabled: {
43
+ true: "pointer-events-none",
44
+ },
45
+ dir: {
46
+ rtl: "flex-row",
47
+ ltr: "flex-row-reverse"
48
+ },
49
+ variant: variantStyle,
50
+ },
51
+ defaultVariants: {
52
+ variant: "primary",
53
+ size: "md",
54
+ },
55
+ }
56
+ );
@@ -0,0 +1,103 @@
1
+ import { fireEvent, render, screen } from "@testing-library/react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { Button } from ".";
4
+
5
+ // Mocking Radix Slot to make it render as span for testing purposes
6
+ vi.mock("@radix-ui/react-slot", () => ({
7
+ Slot: ({ children, ...props }: any) => <span {...props}>{children}</span>,
8
+ }));
9
+
10
+ describe("Button Component", () => {
11
+ it("renders button with label", () => {
12
+ render(<Button>Click Me</Button>);
13
+ const button = screen.getByText("Click Me")
14
+ expect(button).not.toBeDisabled();
15
+ expect(button).toBeDefined();
16
+ });
17
+ it("renders with left and right icons", () => {
18
+ render(<Button leftIcon={<span data-testid="left-icon" />} rightIcon={<span data-testid="right-icon" />}>
19
+ With Icons
20
+ </Button>);
21
+ expect(screen.getByTestId("left-icon")).toBeInTheDocument();
22
+ expect(screen.getByTestId("right-icon")).toBeInTheDocument();
23
+ expect(screen.getByText("With Icons")).toBeInTheDocument();
24
+ });
25
+
26
+ it("renders button with disabled state is true", () => {
27
+ const handleClick = vi.fn();
28
+ render(<Button disabled onClick={handleClick}>Click Me</Button>);
29
+ const button = screen.getByRole("button");
30
+ expect(button).toBeDisabled();
31
+
32
+ fireEvent.click(button);
33
+ expect(handleClick).not.toHaveBeenCalled();
34
+ });
35
+
36
+ it("renders button with disabled state is false", () => {
37
+ const handleClick = vi.fn();
38
+ render(<Button onClick={handleClick}>Click Me</Button>);
39
+ const button = screen.getByRole("button");
40
+ expect(button).not.toBeDisabled();
41
+
42
+ fireEvent.click(button);
43
+ expect(handleClick).toHaveBeenCalledTimes(1);
44
+ });
45
+
46
+ it("renders button with loading state", () => {
47
+ render(<Button loading size={"lg"}>Click Me</Button>);
48
+ const button = screen.getByRole("button");
49
+ expect(button).toHaveClass("pointer-events-none")
50
+ expect(screen.getByLabelText("loading")).toBeDefined();
51
+ expect(screen.getByLabelText("loading")).toHaveClass("w-6 h-6");
52
+ });
53
+
54
+ it("does not call onClick when button is loading", () => {
55
+ const handleClick = vi.fn();
56
+ render(
57
+ <Button loading onClick={handleClick}>
58
+ Click
59
+ </Button>
60
+ );
61
+ fireEvent.click(screen.getByRole("button"));
62
+ expect(handleClick).not.toHaveBeenCalled();
63
+ });
64
+
65
+ it("calls onClick handler when clicked", () => {
66
+ const handleClick = vi.fn();
67
+ render(<Button onClick={handleClick}>Click</Button>);
68
+ fireEvent.click(screen.getByRole("button"));
69
+ expect(handleClick).toHaveBeenCalledTimes(1);
70
+ });
71
+
72
+ it("renders Spinner with default 'md' size when size prop is omitted", () => {
73
+ render(<Button loading>Loading</Button>);
74
+ const spinner = screen.getByLabelText("loading");
75
+ expect(spinner).toHaveClass("h-5 w-5"); // 'md' corresponds to h-5 w-5
76
+ });
77
+
78
+ it("renders button with custom className", () => {
79
+ render(<Button className="custom-class">Click Me</Button>);
80
+ const button = screen.getByRole("button");
81
+ expect(button).toHaveClass("custom-class");
82
+ });
83
+
84
+ it("renders button with type 'submit'", () => {
85
+ render(<Button type="submit">Submit</Button>);
86
+ const button = screen.getByRole("button");
87
+ expect(button).toHaveAttribute("type", "submit");
88
+ });
89
+
90
+ it("renders button with tooltip", () => {
91
+ render(<Button aria-label="Tooltip text">Hover Me</Button>);
92
+ const button = screen.getByRole("button");
93
+ expect(button).toHaveAttribute("aria-label", "Tooltip text");
94
+ });
95
+ it("renders as Slot when asChild is true", () => {
96
+ render(<Button asChild><a href="/test">Link</a></Button>);
97
+ const slotElement = screen.getByText("Link");
98
+ expect(slotElement).toBeInTheDocument();
99
+ // Since we mocked Slot as span, expect a span
100
+ expect(slotElement.parentElement?.tagName.toLowerCase()).toBe("span");
101
+ });
102
+
103
+ });
@@ -0,0 +1,14 @@
1
+ export type ButtonProps = {
2
+ variant?: "primary" | "secondaryGray" | "secondaryColor" | "tertiaryGray" | "tertiaryColor" | "linkGray" | "linkColor";
3
+ size?: "sm" | "md" | "lg" | "xl" | "2xl";
4
+ fullWidth?: boolean;
5
+ loading?: boolean;
6
+ leftIcon?: React.ReactNode;
7
+ rightIcon?: React.ReactNode;
8
+ onClick?: () => void;
9
+ asChild?: boolean;
10
+ disabled?: boolean;
11
+ children?: React.ReactNode;
12
+ className?: string;
13
+ type?: "button" | "submit" | "reset";
14
+ };
@@ -0,0 +1,70 @@
1
+ import { Slot } from "@radix-ui/react-slot";
2
+ import clsx from "clsx";
3
+ import React from "react";
4
+ import { ButtonProps } from "./_.types";
5
+ import { buttonVariants } from "./_.style";
6
+
7
+ // Example spinner
8
+ const Spinner = ({ size = "sm" }: { size?: "sm" | "md" | "lg" | "xl" | "2xl" }) => {
9
+ const sizeMap = {
10
+ sm: "h-4 w-4",
11
+ md: "h-5 w-5",
12
+ lg: "h-6 w-6",
13
+ xl: "h-7 w-7",
14
+ '2xl': "h-7 w-7",
15
+ };
16
+ return (
17
+ <div
18
+ aria-label="loading"
19
+ className={clsx(
20
+ "border-2 border-t-transparent rounded-full animate-spin",
21
+ sizeMap[size]
22
+ )}
23
+ />
24
+ );
25
+ };
26
+
27
+
28
+ export const Button: React.FC<ButtonProps> = ({
29
+ variant = "primary",
30
+ size = "md",
31
+ fullWidth,
32
+ disabled = false,
33
+ loading = false,
34
+ leftIcon,
35
+ rightIcon,
36
+ children,
37
+ className,
38
+ onClick,
39
+ asChild = false,
40
+ ...props
41
+ }) => {
42
+ const Comp = asChild ? Slot : "button";
43
+ return (
44
+ <Comp
45
+ className={buttonVariants({
46
+ variant,
47
+ size,
48
+ fullWidth,
49
+ disabled: disabled,
50
+ className,
51
+ loading: loading,
52
+ })}
53
+ disabled={disabled}
54
+ onClick={loading ? () => {} : onClick}
55
+ {...(props as React.ButtonHTMLAttributes<HTMLButtonElement>)}
56
+ >
57
+ {loading && (
58
+ <div className="absolute inset-0 flex justify-center items-center">
59
+ <Spinner size={size ?? "md"} />
60
+ </div>
61
+ )}
62
+ <span className={clsx({ "invisible": loading }, "flex items-center gap-2")}>
63
+ {leftIcon}
64
+ {children}
65
+ {rightIcon}
66
+ </span>
67
+
68
+ </Comp>
69
+ );
70
+ };