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,51 @@
1
+ import { inputTagsProps } from "@/components/InputTags/_.types";
2
+ import type { StoryObj } from "@storybook/react-vite";
3
+ import { Meta } from "@storybook/react-vite";
4
+ import { InputTags } from "./";
5
+
6
+ export default {
7
+ title: "Components/InputTags",
8
+ component: InputTags,
9
+ tags: ["autodocs"],
10
+ argTypes: {
11
+ disabled: {
12
+ control: { type: "radio" },
13
+ options: [true, false]
14
+ },
15
+ helpIcon: {
16
+ control: { type: "radio" },
17
+ options: [true, false]
18
+ },
19
+ size: {
20
+ control: { type: "radio" },
21
+ options: ["sm", "md"],
22
+ },
23
+ destructive: {
24
+ control: { type: "radio" },
25
+ options: [true, false]
26
+ },
27
+ },
28
+ args: {
29
+ disabled: false,
30
+ size: "sm",
31
+ label: "Email",
32
+ inputType: "default",
33
+ required: true,
34
+ hintText: "This is a hint text to help user.",
35
+ placeholder: "Enter your email",
36
+ destructive: false,
37
+ destructiveText: "error text",
38
+ helpIcon: true,
39
+ tooltipProps: {
40
+ text: "This is a tooltip",
41
+ description: "Tooltips are used to describe or identify an element. In most scenarios",
42
+ position: "top",
43
+ dir: "ltr",
44
+ descriptionClassName: "max-w-[260px] w-full",
45
+ },
46
+ },
47
+ } as Meta<typeof InputTags>;
48
+
49
+ type Story = StoryObj<inputTagsProps>;
50
+
51
+ export const Default: Story = {};
@@ -0,0 +1,28 @@
1
+ import {cva} from "class-variance-authority";
2
+
3
+ const baseStyles =
4
+ "rounded-md px-2 shadow-xs bg-white border border-rborder-primary disabled:border-rborder-disabled flex flex-wrap " +
5
+ "focus:outline-2 focus:outline-rborder-brand disabled:bg-rbg-disabled-subtle relative min-h-[36px] h-fit w-full gap-2";
6
+
7
+ const sizeStyle = {
8
+ sm: "h-9 py-2",
9
+ md: "h-10 pt-2 pb-2.5",
10
+ };
11
+
12
+ const destructiveStyles = "!border-2 !border-rborder-error focus:!outline-0"
13
+
14
+ const disableStyles = "bg-rbg-disabled-subtle border-rborder-disabled"
15
+
16
+ const inputTagsVariants = cva(
17
+ baseStyles,
18
+ {
19
+ variants: {
20
+ size: sizeStyle,
21
+ },
22
+ defaultVariants: {
23
+ size: "sm",
24
+ },
25
+ }
26
+ )
27
+
28
+ export {baseStyles, sizeStyle, destructiveStyles,disableStyles, inputTagsVariants};
@@ -0,0 +1,123 @@
1
+ import "@testing-library/jest-dom";
2
+ import { fireEvent, render, screen } from "@testing-library/react";
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+ import { InputTags } from ".";
5
+ import type { inputTagsProps } from "./_.types";
6
+
7
+ const createInputTagsProps = (overrides: Partial<inputTagsProps> = {}): inputTagsProps => ({
8
+ label: "Email",
9
+ id: "email-input",
10
+ size: "sm",
11
+ destructive: false,
12
+ required: true,
13
+ hintText: "This is a hint text to help user.",
14
+ placeholder: "Enter your email",
15
+ helpIcon: true,
16
+ tooltipProps: {
17
+ text: "This is a tooltip",
18
+ description: "Tooltips are used to describe or identify an element. In most scenarios",
19
+ position: "top",
20
+ dir: "ltr",
21
+ descriptionClassName: "max-w-[260px] w-full",
22
+ },
23
+ ...overrides,
24
+ });
25
+
26
+ describe("InputTags Component", () => {
27
+ beforeEach(() => {
28
+ vi.useFakeTimers();
29
+ });
30
+
31
+ afterEach(() => {
32
+ vi.runOnlyPendingTimers();
33
+ vi.useRealTimers();
34
+ vi.resetAllMocks();
35
+ });
36
+
37
+ it("renders the input with label and placeholder", () => {
38
+ render(<InputTags {...createInputTagsProps()} />);
39
+ const input = screen.getByRole("textbox");
40
+ expect(input).toBeInTheDocument();
41
+ expect(screen.getByText("Email")).toBeInTheDocument();
42
+ });
43
+
44
+ it("renders as disabled when 'disabled' prop is true", () => {
45
+ render(<InputTags {...createInputTagsProps({ disabled: true })} />);
46
+ const input = screen.getByRole("textbox");
47
+ expect(input).toBeDisabled();
48
+ });
49
+
50
+ it("renders correctly with inputType 'tags'", () => {
51
+ render(<InputTags {...createInputTagsProps()} />);
52
+ const input = screen.getByRole("textbox");
53
+ expect(input).toBeInTheDocument();
54
+ });
55
+
56
+ it("adds a tag when input is not empty and Enter is pressed", () => {
57
+ render(<InputTags {...createInputTagsProps()} />);
58
+ const input = screen.getByRole("textbox") as HTMLInputElement;
59
+ fireEvent.change(input, { target: { value: "tag1" } });
60
+ fireEvent.keyDown(input, { key: "Enter" });
61
+ expect(screen.getByText("tag1")).toBeInTheDocument();
62
+ });
63
+
64
+ it("removes a tag when remove button is clicked", () => {
65
+ render(<InputTags {...createInputTagsProps()} />);
66
+ const input = screen.getByRole("textbox") as HTMLInputElement;
67
+
68
+ fireEvent.change(input, { target: { value: "tag1" } });
69
+ fireEvent.keyDown(input, { key: "Enter" });
70
+
71
+ // Use query by data-testid to target the specific remove button
72
+ const removeButton = screen.getByTestId("remove-tag-0"); // Update with correct index if necessary
73
+ fireEvent.click(removeButton);
74
+
75
+ // Check that the tag is no longer in the document
76
+ expect(screen.queryByText("tag1")).not.toBeInTheDocument();
77
+ });
78
+
79
+ it("renders destructive state with destructive text", () => {
80
+ render(<InputTags {...createInputTagsProps({ destructive: true, destructiveText: "Destructive action" })} />);
81
+ const destructiveText = screen.getByText("Destructive action");
82
+ expect(destructiveText).toBeInTheDocument();
83
+ });
84
+
85
+ it("does not render destructive text when destructive is false", () => {
86
+ render(<InputTags {...createInputTagsProps({ destructive: false })} />);
87
+ const destructiveText = screen.queryByText("Destructive action");
88
+ expect(destructiveText).not.toBeInTheDocument();
89
+ });
90
+
91
+ it("adds a tag when 'Enter' key is pressed", () => {
92
+ render(<InputTags {...createInputTagsProps()} />);
93
+ const input = screen.getByRole("textbox") as HTMLInputElement;
94
+
95
+ // Simulate typing into the input field
96
+ fireEvent.change(input, { target: { value: "tag1" } });
97
+
98
+ // Simulate pressing the 'Enter' key
99
+ fireEvent.keyDown(input, { key: "Enter" });
100
+
101
+ // Check if the tag is added to the document
102
+ expect(screen.getByText("tag1")).toBeInTheDocument();
103
+ });
104
+
105
+ it("removes a tag when 'Backspace' key is pressed with empty input", () => {
106
+ render(<InputTags {...createInputTagsProps()} />);
107
+ const input = screen.getByRole("textbox") as HTMLInputElement;
108
+
109
+ // Add a tag first
110
+ fireEvent.change(input, { target: { value: "tag1" } });
111
+ fireEvent.keyDown(input, { key: "Enter" });
112
+
113
+ // Check if the tag is in the document before Backspace
114
+ expect(screen.getByText("tag1")).toBeInTheDocument();
115
+
116
+ // Simulate pressing the 'Backspace' key when the input is empty
117
+ fireEvent.change(input, { target: { value: "" } });
118
+ fireEvent.keyDown(input, { key: "Backspace" });
119
+
120
+ // Check if the tag is removed from the document
121
+ expect(screen.queryByText("tag1")).not.toBeInTheDocument();
122
+ });
123
+ });
@@ -0,0 +1,26 @@
1
+ import { Position } from "../Tooltip/_.types";
2
+
3
+ export type inputTagsProps = {
4
+ className?: string;
5
+ tooltipProps?: {
6
+ text: string,
7
+ description?: string,
8
+ position?: Position,
9
+ dir?: "rtl" | "ltr" | undefined,
10
+ descriptionClassName?: string,
11
+ };
12
+ size?: "sm" | "md";
13
+ destructive?: boolean;
14
+ destructiveText?: string;
15
+ dir?: "ltr" | "rtl";
16
+ label?: string;
17
+ required?: boolean;
18
+ hintText?: string;
19
+ helpIcon?: boolean;
20
+ iconSwap?: boolean;
21
+ disabled?: boolean;
22
+ id?: string;
23
+ placeholder?: string;
24
+ name?: string;
25
+ onChange?: ((tags: string[]) => void) | (() => void) | undefined;
26
+ };
@@ -0,0 +1,140 @@
1
+ import { destructiveStyles, disableStyles, inputTagsVariants } from "@/components/InputTags/_.style";
2
+ import { inputTagsProps } from "@/components/InputTags/_.types";
3
+ import { InputLabel } from "@/components/ui/input-component/input-label";
4
+ import AttentionMark from "@/icons/general/attention-mark";
5
+ import CircleQuestionMark from "@/icons/general/circle-question-mark";
6
+ import Remove from "@/icons/general/remove";
7
+ import { cn } from "@/lib/utils";
8
+ import React, { useCallback, useState } from "react";
9
+ import { TooltipWrapper } from "../Tooltip";
10
+
11
+ export const InputTags = (
12
+ {
13
+ size = "sm",
14
+ disabled = false,
15
+ id,
16
+ label,
17
+ required = false,
18
+ destructive = false,
19
+ destructiveText,
20
+ hintText,
21
+ placeholder,
22
+ helpIcon = false,
23
+ className,
24
+ onChange,
25
+ tooltipProps,
26
+ name,
27
+ }: Readonly<inputTagsProps>) => {
28
+
29
+ const [tags, setTags] = useState<string[]>([]);
30
+ const [input, setInput] = useState<string>("");
31
+
32
+ const addTag = useCallback(() => {
33
+ const trimmed = input.trim();
34
+ if (trimmed && !tags.includes(trimmed)) {
35
+ const newTags = [...tags, trimmed];
36
+ setTags(newTags);
37
+ onChange?.(newTags);
38
+ }
39
+ setInput("");
40
+ }, [input, tags, setTags, setInput, onChange]);
41
+
42
+ const removeTag = useCallback((index: number) => {
43
+ const newTags = tags.filter((_, i) => i !== index);
44
+ setTags(newTags);
45
+ onChange?.(newTags);
46
+ }, [tags, setTags, onChange]);
47
+
48
+ const tagOnKeyDownHandler = useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {
49
+ if (e.key === "Enter") {
50
+ e.preventDefault();
51
+ addTag();
52
+ } else if (e.key === "Backspace" && input === "") {
53
+ removeTag(tags?.length - 1);
54
+ }
55
+ }, [addTag, input, tags, removeTag]);
56
+
57
+ return (
58
+ <div className="flex flex-col gap-1">
59
+ <InputLabel
60
+ id={id}
61
+ label={label}
62
+ required={required}
63
+ />
64
+ <div className="group relative w-full">
65
+ <div
66
+ className={cn(
67
+ inputTagsVariants({ size }),
68
+ className,
69
+ destructive === true && destructiveStyles,
70
+ disabled && disableStyles,
71
+ "group-focus-within:outline-2 group-focus-within:outline-rborder-brand",
72
+ destructive && "bg-rbg-disabled-subtle border-rborder-disabled"
73
+ )}
74
+ >
75
+ {
76
+ tags?.map((tag, index) => (
77
+ <span
78
+ key={index}
79
+ className="flex items-center text-xs text-rtext-secondary-700 px-1 bg-white
80
+ rounded-sm border border-rborder-primary"
81
+ >
82
+ <span className="pt-px">{tag}</span>
83
+ <button
84
+ onClick={() => removeTag(index)}
85
+ className="ms-1 my-auto cursor-pointer disabled:cursor-not-allowed"
86
+ disabled={disabled}
87
+ data-testid={`remove-tag-${index}`}
88
+ >
89
+ <Remove />
90
+ </button>
91
+ </span>
92
+ ))
93
+ }
94
+ <input
95
+ name={name}
96
+ disabled={disabled}
97
+ className="outline-none text-sm bg-transparent disabled:cursor-not-allowed"
98
+ value={input}
99
+ onChange={(e) => setInput(e.target.value)}
100
+ onKeyDown={tagOnKeyDownHandler}
101
+ placeholder={tags?.length === 0 ? placeholder : ""}
102
+ />
103
+
104
+ {helpIcon && tooltipProps && destructive === false && (
105
+ <TooltipWrapper
106
+ content={tooltipProps?.text}
107
+ description={tooltipProps?.description}
108
+ position={tooltipProps?.position}
109
+ dir={tooltipProps?.dir}
110
+ descriptionClassName={tooltipProps?.descriptionClassName}
111
+ >
112
+ <div
113
+ data-testid="tooltip-button"
114
+ className="absolute top-1/2 -translate-y-1/2 ltr:right-2 rtl:left-2 w-fit"
115
+ >
116
+ <CircleQuestionMark />
117
+ </div>
118
+ </TooltipWrapper>
119
+ )}
120
+
121
+ {
122
+ destructive === true &&
123
+ <div className="absolute top-1/2 -translate-y-1/2 ltr:right-2 rtl:left-2 w-fit">
124
+ <AttentionMark />
125
+ </div>
126
+ }
127
+
128
+ </div>
129
+ </div>
130
+ {
131
+ (destructive === false && hintText) &&
132
+ <p className="text-sm text-rtext-tertiary-600">{hintText}</p>
133
+ }
134
+ {
135
+ (destructive && destructiveText) &&
136
+ <p className="text-sm text-rtext-error-primary-600">{destructiveText}</p>
137
+ }
138
+ </div>
139
+ );
140
+ }
@@ -0,0 +1,79 @@
1
+ import { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { Message } from "./index";
3
+ import { Avatar } from "../Avatar";
4
+
5
+ const meta: Meta<typeof Message> = {
6
+ component: Message,
7
+ title: "Components/Message",
8
+ tags: ["autodocs"],
9
+ argTypes: {
10
+ variant: {
11
+ control: "radio",
12
+ options: ["primary", "gray", "white"],
13
+ },
14
+ sender: {
15
+ control: "radio",
16
+ options: ["user", "admin"],
17
+ },
18
+ dir: {
19
+ control: "radio",
20
+ options: ["ltr", "rtl"],
21
+ },
22
+ },
23
+ };
24
+
25
+ export default meta;
26
+
27
+ type Story = StoryObj<typeof Message>;
28
+
29
+ const avatar = (
30
+ <Avatar
31
+ type="single"
32
+ size="md"
33
+ img={{
34
+ src: "https://randomuser.me/api/portraits/women/44.jpg",
35
+ alt: "Alice",
36
+ }}
37
+ iconBorder={false}
38
+ notifyIcon={false}
39
+ />
40
+ );
41
+
42
+ export const LTR_User: Story = {
43
+ args: {
44
+ variant: "primary",
45
+ sender: "user",
46
+ senderName: "Alice",
47
+ message: "Here's the document you asked for.",
48
+ date: "April 16, 2025",
49
+ dir: "ltr",
50
+ avatar,
51
+ },
52
+ };
53
+
54
+ export const RTL_Admin: Story = {
55
+ args: {
56
+ variant: "gray",
57
+ sender: "admin",
58
+ senderName: "ادمین",
59
+ message: "لطفاً قبل از ارسال، راهنما را بررسی کنید.",
60
+ date: "16 آوریل 2025",
61
+ dir: "rtl",
62
+ avatar,
63
+ },
64
+ };
65
+
66
+ export const WithChildren: Story = {
67
+ args: {
68
+ variant: "white",
69
+ sender: "admin",
70
+ senderName: "System",
71
+ date: "Today",
72
+ dir: "ltr",
73
+ message: "Message with children:",
74
+ avatar,
75
+ children: (
76
+ <div className="text-sm text-blue-600">Extra content inside message</div>
77
+ ),
78
+ },
79
+ };
@@ -0,0 +1,87 @@
1
+ import { cva } from "class-variance-authority";
2
+
3
+ export const baseStyles = "w-full flex flex-col py-2.5 px-3.5 gap-2";
4
+ const variantStyle = {
5
+ primary: "bg-rbg-brand-solid text-rtext-white text-md font-normal",
6
+ gray: "bg-rbg-secondary border border-rborder-secondary text-rtext-primary-900 text-md font-normal",
7
+ white: "bg-rbg-primary border border-rborder-secondary",
8
+ };
9
+ export const messageVariants = cva(baseStyles, {
10
+ variants: {
11
+ sender: {
12
+ admin: "",
13
+ user: "",
14
+ },
15
+ variant: variantStyle,
16
+ direction: {
17
+ ltr: "",
18
+ rtl: "",
19
+ },
20
+ },
21
+ compoundVariants: [
22
+ {
23
+ sender: "admin",
24
+ direction: "ltr",
25
+ class: "rounded-tl-md rounded-tr-none rounded-br-md rounded-bl-md ",
26
+ },
27
+ {
28
+ sender: "user",
29
+ direction: "ltr",
30
+ class: "rounded-tl-none rounded-tr-md rounded-br-md rounded-bl-md",
31
+ },
32
+
33
+ {
34
+ sender: "admin",
35
+ direction: "rtl",
36
+ class: " rounded-tl-md rounded-tr-none rounded-br-md rounded-bl-md",
37
+ },
38
+ {
39
+ sender: "user",
40
+ direction: "rtl",
41
+ class: "rounded-tl-none rounded-tr-md rounded-br-md rounded-bl-md",
42
+ },
43
+ ],
44
+ defaultVariants: {
45
+ variant: "primary",
46
+ direction: "rtl", // default fallback
47
+ },
48
+ });
49
+ export const messageDirectionVariants = cva("", {
50
+ variants: {
51
+ direction: {
52
+ ltr: "",
53
+ rtl: "",
54
+ },
55
+ sender: {
56
+ admin: "",
57
+ user: "",
58
+ },
59
+ },
60
+ compoundVariants: [
61
+ {
62
+ sender: "admin",
63
+ direction: "ltr",
64
+ class: "flex-row-reverse",
65
+ },
66
+ {
67
+ sender: "admin",
68
+ direction: "rtl",
69
+ class: "flex-row",
70
+ },
71
+ {
72
+ sender: "user",
73
+ direction: "ltr",
74
+ class: "flex-row",
75
+ },
76
+
77
+ {
78
+ sender: "user",
79
+ direction: "rtl",
80
+ class: "flex-row-reverse",
81
+ },
82
+ ],
83
+ defaultVariants: {
84
+ direction: "ltr",
85
+ sender: "user",
86
+ },
87
+ });
@@ -0,0 +1,73 @@
1
+ // Message.test.tsx
2
+ import { Avatar } from "@/components/Avatar"; // adjust to match your actual import
3
+ import { render, screen } from "@testing-library/react";
4
+ import { describe, expect, it } from "vitest";
5
+ import { Message } from ".";
6
+ import { MessageProps } from "./_.types";
7
+
8
+ describe("Message component", () => {
9
+ const baseProps: MessageProps = {
10
+ variant: "primary",
11
+ sender: "user",
12
+ senderName: "Alice",
13
+ message: "Hello, world!",
14
+ date: "April 16, 2025",
15
+ className: "",
16
+ children: null,
17
+ avatar: (
18
+ <Avatar
19
+ type="single"
20
+ size="md"
21
+ img={{ src: "https://example.com/avatar.jpg", alt: "Alice" }}
22
+ iconBorder={false}
23
+ notifyIcon={false}
24
+ addBtnAction={() => { }}
25
+ />
26
+ ),
27
+ };
28
+ it("renders message with LTR direction and applies correct classes", () => {
29
+ render(<Message {...baseProps} dir="ltr" />);
30
+
31
+ expect(screen.getByTestId("messageWrapper")).toHaveClass("flex-row");
32
+ });
33
+
34
+ it("renders message with RTL direction and applies correct classes", () => {
35
+ render(<Message {...baseProps} dir="rtl" />);
36
+
37
+ expect(screen.getByTestId("messageWrapper")).toHaveClass(
38
+ "flex-row-reverse"
39
+ );
40
+ });
41
+
42
+ it("renders avatar and/or children", () => {
43
+ const { container, getByLabelText } = render(
44
+ <Message
45
+ {...baseProps}
46
+ avatar={
47
+ <Avatar
48
+ type="single"
49
+ size="md"
50
+ img={{ src: "https://example.com/avatar.jpg", alt: "Alice" }}
51
+ iconBorder={false}
52
+ notifyIcon={false}
53
+ addBtnAction={() => { }}
54
+ />
55
+ }
56
+ children={<span aria-label="children">children</span>}
57
+ message="No avatar or chldrn"
58
+ />
59
+ );
60
+
61
+ const avatarElement = container.querySelector(
62
+ 'img[src="https://example.com/avatar.jpg"]'
63
+ );
64
+ expect(avatarElement).toBeInTheDocument();
65
+ const childrenElement = getByLabelText("children");
66
+ expect(childrenElement).toBeInTheDocument();
67
+ });
68
+
69
+ it("falls back to document direction when dir is not passed", () => {
70
+ render(<Message {...baseProps} dir={undefined} />);
71
+ expect(screen.getByText("Hello, world!")).toBeInTheDocument();
72
+ });
73
+ });
@@ -0,0 +1,13 @@
1
+ import { Avatar } from "../Avatar";
2
+
3
+ export interface MessageProps {
4
+ variant?: "primary" | "gray" | "white";
5
+ sender: "user" | "admin";
6
+ senderName: string;
7
+ message?: string;
8
+ children?: React.ReactNode;
9
+ avatar?: React.ReactElement<typeof Avatar> | null;
10
+ date: string;
11
+ className?: string;
12
+ dir?: "rtl" | "ltr";
13
+ }
@@ -0,0 +1,57 @@
1
+ import { messageDirectionVariants, messageVariants } from "./_.style";
2
+ import { cn } from "@/lib/utils";
3
+ import { MessageProps } from "./_.types";
4
+
5
+ export const Message = ({
6
+ variant,
7
+ sender,
8
+ senderName,
9
+ message,
10
+ children,
11
+ avatar,
12
+ date,
13
+ className,
14
+ dir,
15
+ }: MessageProps) => {
16
+ const direction: "ltr" | "rtl" =
17
+ dir ?? (document?.body?.getAttribute("dir") === "rtl" ? "rtl" : "ltr");
18
+ return (
19
+ <div
20
+ className={cn(
21
+ "w-full flex ",
22
+ messageDirectionVariants({ sender, direction })
23
+ )}
24
+ data-testid="messageWrapper"
25
+ >
26
+ <div
27
+ className={cn(
28
+ "w-fit max-w-4/5 flex gap-3",
29
+ messageDirectionVariants({ sender, direction }),
30
+ className
31
+ )}
32
+ data-testid="messageComp"
33
+ >
34
+ {avatar ?? null}
35
+ <div className="w-full flex flex-col gap-1.5">
36
+ <div
37
+ className={cn(
38
+ "w-full flex justify-between items-center",
39
+ messageDirectionVariants({ sender, direction })
40
+ )}
41
+ >
42
+ <span className="text-rtext-secondary-700 text-sm font-medium">
43
+ {senderName}
44
+ </span>
45
+ <span className="text-rtext-tertiary-600 text-xs font-normal">
46
+ {date}
47
+ </span>
48
+ </div>
49
+ <div className={cn(messageVariants({ variant, sender, direction }))}>
50
+ {message && <span className="">{message}</span>}
51
+ {children ?? null}
52
+ </div>
53
+ </div>
54
+ </div>
55
+ </div>
56
+ );
57
+ };