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.
- package/.gitlab-ci.yml +43 -0
- package/.storybook/main.ts +15 -0
- package/.storybook/preview.ts +15 -0
- package/README.md +254 -0
- package/components.json +21 -0
- package/dist/Avatar.png +0 -0
- package/dist/assets/index-CCq7hmG3.js +186 -0
- package/dist/assets/index-Mg-hjQGu.css +1 -0
- package/dist/index.html +15 -0
- package/dist/test.png +0 -0
- package/dist/vite.svg +1 -0
- package/eslint.config.js +29 -0
- package/index.html +14 -0
- package/package.json +102 -0
- package/postcss.config.mjs +11 -0
- package/rollup.config.mjs +55 -0
- package/src/assets/react.svg +1 -0
- package/src/assets/style/animation.css +27 -0
- package/src/assets/style/box-shadow.css +25 -0
- package/src/assets/style/colors.css +402 -0
- package/src/assets/style/dark-theme.css +288 -0
- package/src/assets/style/font-size.css +14 -0
- package/src/assets/style/gradient.css +3 -0
- package/src/assets/style/index.css +12 -0
- package/src/assets/style/light-theme.css +148 -0
- package/src/assets/style/line-height.css +13 -0
- package/src/assets/style/max-width.css +5 -0
- package/src/assets/style/radius.css +13 -0
- package/src/assets/style/utility-colors.css +166 -0
- package/src/components/Accordion/_.stories.tsx +75 -0
- package/src/components/Accordion/_.test.tsx +77 -0
- package/src/components/Accordion/index.tsx +47 -0
- package/src/components/Accordion/type.ts +24 -0
- package/src/components/Avatar/_.stories.tsx +179 -0
- package/src/components/Avatar/_.style.ts +40 -0
- package/src/components/Avatar/_.test.tsx +150 -0
- package/src/components/Avatar/_.types.ts +66 -0
- package/src/components/Avatar/index.tsx +63 -0
- package/src/components/Badge/_.stories.tsx +75 -0
- package/src/components/Badge/_.style.ts +53 -0
- package/src/components/Badge/_.test.tsx +27 -0
- package/src/components/Badge/_.types.ts +11 -0
- package/src/components/Badge/index.tsx +42 -0
- package/src/components/Breadcrumbs/_.stories.tsx +95 -0
- package/src/components/Breadcrumbs/_.test.tsx +29 -0
- package/src/components/Breadcrumbs/_.type.ts +15 -0
- package/src/components/Breadcrumbs/index.tsx +103 -0
- package/src/components/Button/_.stories.tsx +85 -0
- package/src/components/Button/_.style.ts +56 -0
- package/src/components/Button/_.test.tsx +103 -0
- package/src/components/Button/_.types.ts +14 -0
- package/src/components/Button/index.tsx +70 -0
- package/src/components/Checkbox/_.stories.tsx +96 -0
- package/src/components/Checkbox/_.style.ts +24 -0
- package/src/components/Checkbox/_.test.tsx +85 -0
- package/src/components/Checkbox/_.types.ts +23 -0
- package/src/components/Checkbox/index.tsx +93 -0
- package/src/components/CheckboxGroup/PaymentCard/_.stories.tsx +104 -0
- package/src/components/CheckboxGroup/PaymentCard/_.style.ts +28 -0
- package/src/components/CheckboxGroup/PaymentCard/_.test.tsx +58 -0
- package/src/components/CheckboxGroup/PaymentCard/_.types.ts +28 -0
- package/src/components/CheckboxGroup/PaymentCard/index.tsx +71 -0
- package/src/components/CheckboxGroup/PlanCard/_.stories.tsx +165 -0
- package/src/components/CheckboxGroup/PlanCard/_.style.ts +32 -0
- package/src/components/CheckboxGroup/PlanCard/_.test.tsx +54 -0
- package/src/components/CheckboxGroup/PlanCard/_.types.ts +35 -0
- package/src/components/CheckboxGroup/PlanCard/index.tsx +53 -0
- package/src/components/CheckboxGroup/UserCard/_.stories.tsx +89 -0
- package/src/components/CheckboxGroup/UserCard/_.style.ts +42 -0
- package/src/components/CheckboxGroup/UserCard/_.test.tsx +66 -0
- package/src/components/CheckboxGroup/UserCard/_.types.ts +26 -0
- package/src/components/CheckboxGroup/UserCard/index.tsx +75 -0
- package/src/components/Dropdown/_.stories.tsx +180 -0
- package/src/components/Dropdown/_.style.ts +108 -0
- package/src/components/Dropdown/_.test.tsx +334 -0
- package/src/components/Dropdown/_.types.ts +12 -0
- package/src/components/Dropdown/index.tsx +130 -0
- package/src/components/FileUpload/_.stories.tsx +74 -0
- package/src/components/FileUpload/_.style.ts +0 -0
- package/src/components/FileUpload/_.test.tsx +222 -0
- package/src/components/FileUpload/_.types.ts +53 -0
- package/src/components/FileUpload/index.tsx +44 -0
- package/src/components/ImageMagnify/_.stories.tsx +226 -0
- package/src/components/ImageMagnify/_.style.ts +109 -0
- package/src/components/ImageMagnify/_.types.ts +44 -0
- package/src/components/ImageMagnify/index.tsx +204 -0
- package/src/components/Input/_.stories.tsx +177 -0
- package/src/components/Input/_.style.ts +79 -0
- package/src/components/Input/_.test.tsx +146 -0
- package/src/components/Input/_.types.ts +66 -0
- package/src/components/Input/index.tsx +231 -0
- package/src/components/InputTags/_.stories.tsx +51 -0
- package/src/components/InputTags/_.style.ts +28 -0
- package/src/components/InputTags/_.test.tsx +123 -0
- package/src/components/InputTags/_.types.ts +26 -0
- package/src/components/InputTags/index.tsx +140 -0
- package/src/components/Message/_.stories.tsx +79 -0
- package/src/components/Message/_.style.ts +87 -0
- package/src/components/Message/_.test.tsx +73 -0
- package/src/components/Message/_.types.ts +13 -0
- package/src/components/Message/index.tsx +57 -0
- package/src/components/Metric/_.stories.tsx +142 -0
- package/src/components/Metric/_.style.ts +14 -0
- package/src/components/Metric/_.test.tsx +166 -0
- package/src/components/Metric/_.types.ts +18 -0
- package/src/components/Metric/index.tsx +100 -0
- package/src/components/Modal/_.stories.tsx +93 -0
- package/src/components/Modal/_.style.ts +31 -0
- package/src/components/Modal/_.test.tsx +90 -0
- package/src/components/Modal/_.types.ts +14 -0
- package/src/components/Modal/index.tsx +82 -0
- package/src/components/Pagination/_.stories.tsx +118 -0
- package/src/components/Pagination/_.test.tsx +51 -0
- package/src/components/Pagination/index.tsx +256 -0
- package/src/components/Pagination/type.ts +48 -0
- package/src/components/PriceSlider/_.stories.tsx +107 -0
- package/src/components/PriceSlider/_.test.tsx +63 -0
- package/src/components/PriceSlider/_.type.tsx +19 -0
- package/src/components/PriceSlider/index.tsx +86 -0
- package/src/components/Progress/_.stories.tsx +93 -0
- package/src/components/Progress/_.style.ts +15 -0
- package/src/components/Progress/_.test.tsx +34 -0
- package/src/components/Progress/_.types.ts +17 -0
- package/src/components/Progress/index.tsx +173 -0
- package/src/components/Radio/_.stories.tsx +391 -0
- package/src/components/Radio/_.style.ts +33 -0
- package/src/components/Radio/_.test.tsx +77 -0
- package/src/components/Radio/_.types.ts +14 -0
- package/src/components/Radio/index.tsx +59 -0
- package/src/components/Select/_.stories.tsx +308 -0
- package/src/components/Select/_.style.ts +5 -0
- package/src/components/Select/_.types.ts +24 -0
- package/src/components/Select/index.tsx +172 -0
- package/src/components/Switch/_.stories.tsx +61 -0
- package/src/components/Switch/_.test.tsx +69 -0
- package/src/components/Switch/_.type.ts +12 -0
- package/src/components/Switch/index.tsx +70 -0
- package/src/components/Tabs/_.stories.tsx +508 -0
- package/src/components/Tabs/_.style.ts +63 -0
- package/src/components/Tabs/_.test.tsx +174 -0
- package/src/components/Tabs/_.type.ts +19 -0
- package/src/components/Tabs/index.tsx +35 -0
- package/src/components/Tag/_.stories.tsx +78 -0
- package/src/components/Tag/_.style.ts +71 -0
- package/src/components/Tag/_.test.tsx +44 -0
- package/src/components/Tag/_.types.ts +27 -0
- package/src/components/Tag/index.tsx +46 -0
- package/src/components/TextArea/_.stories.tsx +62 -0
- package/src/components/TextArea/_.style.ts +11 -0
- package/src/components/TextArea/_.test.tsx +43 -0
- package/src/components/TextArea/_.types.ts +29 -0
- package/src/components/TextArea/index.tsx +83 -0
- package/src/components/Toast/_.style.tsx +27 -0
- package/src/components/Toast/_.type.ts +30 -0
- package/src/components/Toast/_.utils.ts +23 -0
- package/src/components/Toast/container.tsx +171 -0
- package/src/components/Toast/index.tsx +29 -0
- package/src/components/Tooltip/_.stories.tsx +106 -0
- package/src/components/Tooltip/_.style.ts +27 -0
- package/src/components/Tooltip/_.test.tsx +54 -0
- package/src/components/Tooltip/_.types.ts +31 -0
- package/src/components/Tooltip/index.tsx +80 -0
- package/src/components/developers/AmirHossein.tsx +149 -0
- package/src/components/developers/Fardin.tsx +130 -0
- package/src/components/developers/Maryam.tsx +260 -0
- package/src/components/developers/Milad.tsx +431 -0
- package/src/components/developers/Rasoul.tsx +198 -0
- package/src/components/index.ts +28 -0
- package/src/components/ui/accordion.tsx +162 -0
- package/src/components/ui/avatars-component/avatar-description.tsx +30 -0
- package/src/components/ui/avatars-component/avatar-groups.tsx +68 -0
- package/src/components/ui/avatars-component/avatar-single.tsx +50 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/checkbox-group/plan-card/basic/_.test.tsx +66 -0
- package/src/components/ui/checkbox-group/plan-card/basic/index.tsx +70 -0
- package/src/components/ui/checkbox-group/plan-card/with-header/_.test.tsx +110 -0
- package/src/components/ui/checkbox-group/plan-card/with-header/header.test.tsx +96 -0
- package/src/components/ui/checkbox-group/plan-card/with-header/header.tsx +74 -0
- package/src/components/ui/checkbox-group/plan-card/with-header/index.tsx +65 -0
- package/src/components/ui/file-content/File-content.tsx +43 -0
- package/src/components/ui/file-uploader-components/file-uploader-box.tsx +76 -0
- package/src/components/ui/file-uploader-components/file-uploader-item.tsx +64 -0
- package/src/components/ui/icon-wrapper/_.test.tsx +60 -0
- package/src/components/ui/icon-wrapper/index.tsx +19 -0
- package/src/components/ui/input-component/input-label.tsx +11 -0
- package/src/components/ui/number.tsx +18 -0
- package/src/components/ui/pagination/card-minimal-center-align.tsx +96 -0
- package/src/components/ui/pagination/card-minimal-right-aligne.tsx +90 -0
- package/src/components/ui/pagination/default-pagination.tsx +128 -0
- package/src/components/ui/pagination/get-pagination-item.tsx +36 -0
- package/src/components/ui/pagination/pagination-card-button-group-aligned.tsx +94 -0
- package/src/components/ui/pagination/pagination-card-minimal-left-aligned.tsx +90 -0
- package/src/components/ui/pagination/pagination-content.tsx +15 -0
- package/src/components/ui/pagination/pagination-item.tsx +11 -0
- package/src/components/ui/pagination/pagination-link.tsx +42 -0
- package/src/components/ui/tab-components/tabs-content.tsx +15 -0
- package/src/components/ui/tab-components/tabs-list.tsx +27 -0
- package/src/components/ui/tab-components/tabs-trigger.tsx +25 -0
- package/src/components/ui/text-content-wrapper.tsx +36 -0
- package/src/hooks/useClickOutside.ts +23 -0
- package/src/icons/general/ArrowLeft.tsx +31 -0
- package/src/icons/general/ArrowRight.tsx +31 -0
- package/src/icons/general/activity-heart.tsx +31 -0
- package/src/icons/general/activity.tsx +31 -0
- package/src/icons/general/anchor.tsx +31 -0
- package/src/icons/general/archive.tsx +31 -0
- package/src/icons/general/arrow-left.tsx +25 -0
- package/src/icons/general/arrow-right.tsx +25 -0
- package/src/icons/general/asterisk-01.tsx +31 -0
- package/src/icons/general/asterisk-02.tsx +31 -0
- package/src/icons/general/at-sign.tsx +31 -0
- package/src/icons/general/attention-mark.tsx +43 -0
- package/src/icons/general/bookmark-add.tsx +31 -0
- package/src/icons/general/bookmark.tsx +31 -0
- package/src/icons/general/chevron-left.tsx +25 -0
- package/src/icons/general/chevron-right.tsx +25 -0
- package/src/icons/general/circle-minues.tsx +25 -0
- package/src/icons/general/circle-plus.tsx +25 -0
- package/src/icons/general/circle-question-mark.tsx +32 -0
- package/src/icons/general/circle.tsx +32 -0
- package/src/icons/general/copy.tsx +43 -0
- package/src/icons/general/email.tsx +32 -0
- package/src/icons/general/home.tsx +25 -0
- package/src/icons/general/layer.tsx +36 -0
- package/src/icons/general/leading.tsx +19 -0
- package/src/icons/general/master-card.tsx +37 -0
- package/src/icons/general/minus.tsx +36 -0
- package/src/icons/general/plus.tsx +19 -0
- package/src/icons/general/remove.tsx +32 -0
- package/src/icons/general/slash-divider.tsx +26 -0
- package/src/icons/general/tick-box.tsx +37 -0
- package/src/icons/general/trailing.tsx +19 -0
- package/src/icons/general/unkown-format.tsx +25 -0
- package/src/icons/general/visa-card.tsx +38 -0
- package/src/icons/general/x-close.tsx +35 -0
- package/src/icons/icons.type.ts +7 -0
- package/src/index.css +21 -0
- package/src/index.ts +3 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/zIndexUtils.ts +2 -0
- package/src/main.tsx +50 -0
- package/src/vite-env.d.ts +1 -0
- package/tests/setup.ts +8 -0
- package/tsconfig.app.json +31 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +24 -0
- package/tsconfig.rollup.json +12 -0
- package/vite.config.ts +20 -0
- package/vitest.config.ts +47 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
|
|
3
|
+
import { ArrowUpRight, EllipsisVertical } from "lucide-react";
|
|
4
|
+
import { Metrics } from ".";
|
|
5
|
+
import { Button } from "../Button";
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: "Components/Metrics",
|
|
9
|
+
component: Metrics,
|
|
10
|
+
tags: ["autodocs"],
|
|
11
|
+
argTypes: {
|
|
12
|
+
// Optionally add controls for props here
|
|
13
|
+
},
|
|
14
|
+
} satisfies Meta<typeof Metrics>;
|
|
15
|
+
|
|
16
|
+
export default meta;
|
|
17
|
+
type Story = StoryObj<typeof Metrics>;
|
|
18
|
+
|
|
19
|
+
export const Default: Story = {
|
|
20
|
+
args: {
|
|
21
|
+
title: "Total Sales",
|
|
22
|
+
value: "$32,000",
|
|
23
|
+
description: "12% increase from last month",
|
|
24
|
+
icon: <ArrowUpRight className="w-6 h-6 text-green-600" />,
|
|
25
|
+
iconType: "success",
|
|
26
|
+
toggleBtn: (
|
|
27
|
+
<button className="underline text-sm text-rtext-tertiary-600">
|
|
28
|
+
Details
|
|
29
|
+
</button>
|
|
30
|
+
),
|
|
31
|
+
statistic: <span className="text-xs font-bold text-green-500">+12%</span>,
|
|
32
|
+
iconPosition: "topWithTitle",
|
|
33
|
+
statisticPosition: "row-between",
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const WithChartBottom: Story = {
|
|
38
|
+
args: {
|
|
39
|
+
title: "Active Users",
|
|
40
|
+
value: "8,450",
|
|
41
|
+
description: "Stable since last week",
|
|
42
|
+
toggleBtn: (
|
|
43
|
+
<button className="underline text-sm text-rtext-tertiary-600">
|
|
44
|
+
View
|
|
45
|
+
</button>
|
|
46
|
+
),
|
|
47
|
+
chartImg: <img src="/test.png" alt="Chart" className="w-full h-full" />,
|
|
48
|
+
chartPosition: "bottom",
|
|
49
|
+
iconPosition: "hidden",
|
|
50
|
+
statisticPosition: "row-next",
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const WithChartBottomAndActions: Story = {
|
|
55
|
+
args: {
|
|
56
|
+
title: "Total Sales",
|
|
57
|
+
value: "$32,000",
|
|
58
|
+
icon: <ArrowUpRight className="w-6 h-6 text-green-600" />,
|
|
59
|
+
iconType: "secondary",
|
|
60
|
+
iconPosition: "topWithTitle",
|
|
61
|
+
statistic: (
|
|
62
|
+
<span className="text-xs font-medium text-green-700 bg-green-100 rounded px-2 py-0.5">
|
|
63
|
+
+12%
|
|
64
|
+
</span>
|
|
65
|
+
),
|
|
66
|
+
toggleBtn: (
|
|
67
|
+
<Button variant={"primary"}
|
|
68
|
+
className="px-4 py-1.5"
|
|
69
|
+
size={"md"}>
|
|
70
|
+
Details
|
|
71
|
+
</Button>
|
|
72
|
+
),
|
|
73
|
+
actionBtns: [
|
|
74
|
+
<Button
|
|
75
|
+
key="1"
|
|
76
|
+
variant={"primary"}
|
|
77
|
+
className="px-4 py-1.5"
|
|
78
|
+
size={"sm"}
|
|
79
|
+
>
|
|
80
|
+
View
|
|
81
|
+
</Button>,
|
|
82
|
+
<Button
|
|
83
|
+
variant={"secondaryColor"}
|
|
84
|
+
key="2"
|
|
85
|
+
className="px-4 py-1.5"
|
|
86
|
+
size={"sm"}
|
|
87
|
+
>
|
|
88
|
+
Export
|
|
89
|
+
</Button>,
|
|
90
|
+
],
|
|
91
|
+
statisticPosition: "row-next",
|
|
92
|
+
dir: "rtl",
|
|
93
|
+
chartPosition: "bottom",
|
|
94
|
+
chartImg: <img src="/test.png" alt="" className="w-full h-full" />,
|
|
95
|
+
titleType: "bold",
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
export const WithChartSideAndActions: Story = {
|
|
99
|
+
args: {
|
|
100
|
+
title: "Total Sales",
|
|
101
|
+
value: "$32,000",
|
|
102
|
+
icon: <ArrowUpRight className="w-6 h-6 text-green-600" />,
|
|
103
|
+
iconType: "secondary",
|
|
104
|
+
iconPosition: "topWithTitle",
|
|
105
|
+
statistic: (
|
|
106
|
+
<span className="text-xs font-medium text-green-700 bg-green-100 rounded px-2 py-0.5">
|
|
107
|
+
+12%
|
|
108
|
+
</span>
|
|
109
|
+
),
|
|
110
|
+
toggleBtn: (
|
|
111
|
+
<Button variant={"linkGray"}
|
|
112
|
+
className="px-4 py-1.5"
|
|
113
|
+
onClick={() => alert("dropdown")}
|
|
114
|
+
size={"md"}>
|
|
115
|
+
<EllipsisVertical />
|
|
116
|
+
</Button>
|
|
117
|
+
),
|
|
118
|
+
actionBtns: [
|
|
119
|
+
<Button
|
|
120
|
+
key="1"
|
|
121
|
+
variant={"primary"}
|
|
122
|
+
className="px-4 py-1.5"
|
|
123
|
+
size={"sm"}
|
|
124
|
+
>
|
|
125
|
+
View
|
|
126
|
+
</Button>,
|
|
127
|
+
<Button
|
|
128
|
+
variant={"secondaryColor"}
|
|
129
|
+
key="2"
|
|
130
|
+
className="px-4 py-1.5"
|
|
131
|
+
size={"sm"}
|
|
132
|
+
>
|
|
133
|
+
Export
|
|
134
|
+
</Button>,
|
|
135
|
+
],
|
|
136
|
+
statisticPosition: "row-next",
|
|
137
|
+
dir: "rtl",
|
|
138
|
+
chartPosition: "side",
|
|
139
|
+
chartImg: <img src="/test.png" alt="" className="w-full h-full" />,
|
|
140
|
+
titleType: "bold",
|
|
141
|
+
},
|
|
142
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const baseStyles = "flex flex-col h-full items-end gap-2 w-full";
|
|
2
|
+
const iconStyles = {
|
|
3
|
+
success:
|
|
4
|
+
"w-12 h-12 flex items-center justify-center rounded-full bg-rbg-success-secondary",
|
|
5
|
+
notification:
|
|
6
|
+
"w-12 h-12 flex items-center justify-center rounded-full bg-rbg-brand-secondary",
|
|
7
|
+
secondary:
|
|
8
|
+
"w-12 h-12 flex items-center justify-center rounded-lg bg-rbg-primary border border-gray-light-200 shadow-xs-skeuomorphic",
|
|
9
|
+
};
|
|
10
|
+
const titleStyle = {
|
|
11
|
+
normal: "font-medium text-sm text-rtext-tertiary-600",
|
|
12
|
+
bold: "text-rtext-primary-900 font-normal text-md",
|
|
13
|
+
};
|
|
14
|
+
export { baseStyles, iconStyles, titleStyle };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { ArrowUpRight } from "lucide-react";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { Metrics } from ".";
|
|
5
|
+
|
|
6
|
+
describe("Metrics Component", () => {
|
|
7
|
+
it("should render title, value, description and toggle button", () => {
|
|
8
|
+
render(
|
|
9
|
+
<Metrics
|
|
10
|
+
title="Test Title"
|
|
11
|
+
value="100"
|
|
12
|
+
description="Test Description"
|
|
13
|
+
toggleBtn={<button>Toggle</button>}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
expect(screen.getByText("Test Title")).toBeDefined();
|
|
17
|
+
expect(screen.getByText("100")).toBeDefined();
|
|
18
|
+
expect(screen.getByText("Test Description")).toBeDefined();
|
|
19
|
+
expect(screen.getByRole("button", { name: /toggle/i })).toBeDefined();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should render icon when iconPosition is 'topWithTitle'", () => {
|
|
23
|
+
render(
|
|
24
|
+
<Metrics
|
|
25
|
+
title="Icon Test"
|
|
26
|
+
value="200"
|
|
27
|
+
toggleBtn={<button>Toggle</button>}
|
|
28
|
+
iconPosition="topWithTitle"
|
|
29
|
+
icon={<ArrowUpRight data-testid="icon" />}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
// the icon should be rendered when iconPosition is either "topWithTitle" or "topAlone"
|
|
33
|
+
expect(screen.getByTestId("icon")).toBeDefined();
|
|
34
|
+
});
|
|
35
|
+
it("should not render icon when iconPosition is 'hidden'", () => {
|
|
36
|
+
render(
|
|
37
|
+
<Metrics
|
|
38
|
+
title="Icon Test"
|
|
39
|
+
value="200"
|
|
40
|
+
toggleBtn={<button>Toggle</button>}
|
|
41
|
+
iconPosition="hidden"
|
|
42
|
+
icon={<ArrowUpRight data-testid="icon" />}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
// using queryByTestId returns null if the icon is not rendered
|
|
46
|
+
expect(screen.queryByTestId("icon")).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should render chart image when chartPosition is 'bottom'", () => {
|
|
50
|
+
render(
|
|
51
|
+
<Metrics
|
|
52
|
+
title="Chart Test"
|
|
53
|
+
value="300"
|
|
54
|
+
toggleBtn={<button>Toggle</button>}
|
|
55
|
+
chartPosition="bottom"
|
|
56
|
+
chartImg={<img src="/test.png" alt="chart" data-testid="chart" />}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
expect(screen.getByTestId("chart")).toBeDefined();
|
|
60
|
+
expect(screen.getByTestId("chart")).toHaveAttribute("src", "/test.png");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should render chart image when chartPosition is 'side'", () => {
|
|
64
|
+
render(
|
|
65
|
+
<Metrics
|
|
66
|
+
title="Chart Side Test"
|
|
67
|
+
value="400"
|
|
68
|
+
toggleBtn={<button>Toggle</button>}
|
|
69
|
+
chartPosition="side"
|
|
70
|
+
chartImg={
|
|
71
|
+
<img src="/test.png" alt="chart side" data-testid="chart-side" />
|
|
72
|
+
}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
expect(screen.getByTestId("chart-side")).toBeDefined();
|
|
76
|
+
expect(screen.getByTestId("chart-side")).toHaveAttribute(
|
|
77
|
+
"src",
|
|
78
|
+
"/test.png"
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should render action buttons when provided", () => {
|
|
83
|
+
render(
|
|
84
|
+
<Metrics
|
|
85
|
+
title="Actions Test"
|
|
86
|
+
value="500"
|
|
87
|
+
toggleBtn={<button>Toggle</button>}
|
|
88
|
+
actionBtns={[
|
|
89
|
+
<button key="1">Button 1</button>,
|
|
90
|
+
<button key="2">Button 2</button>,
|
|
91
|
+
]}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
expect(screen.getByText("Button 1")).toBeDefined();
|
|
95
|
+
expect(screen.getByText("Button 2")).toBeDefined();
|
|
96
|
+
});
|
|
97
|
+
it("should render the icon correctly when iconPosition is 'iconSide' (Line 26)", () => {
|
|
98
|
+
render(
|
|
99
|
+
<Metrics
|
|
100
|
+
title="Test"
|
|
101
|
+
value="100"
|
|
102
|
+
toggleBtn={<button>Toggle</button>}
|
|
103
|
+
iconPosition="iconSide"
|
|
104
|
+
iconType="secondary"
|
|
105
|
+
icon={<div data-testid="icon">Icon</div>}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
// Line 26: Icon should be rendered inside a div with "self-center" as part of its class.
|
|
109
|
+
const iconElement = screen.getByTestId("icon");
|
|
110
|
+
expect(iconElement).toBeInTheDocument();
|
|
111
|
+
expect(iconElement.parentElement).toHaveClass("self-center");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should render the title in a span when iconPosition is 'topAlone' (Line 51)", () => {
|
|
115
|
+
render(
|
|
116
|
+
<Metrics
|
|
117
|
+
title="Top Alone Title"
|
|
118
|
+
value="200"
|
|
119
|
+
toggleBtn={<button>Toggle</button>}
|
|
120
|
+
iconPosition="topAlone"
|
|
121
|
+
/>
|
|
122
|
+
);
|
|
123
|
+
// Line 51: The title should appear in a span rendered solely for topAlone condition.
|
|
124
|
+
const titleElement = screen.getByText("Top Alone Title");
|
|
125
|
+
expect(titleElement).toBeInTheDocument();
|
|
126
|
+
expect(titleElement.tagName.toLowerCase()).toBe("span");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should apply 'row-below' classes when statisticPosition is 'row-below' (Line 59)", () => {
|
|
130
|
+
render(
|
|
131
|
+
<Metrics
|
|
132
|
+
title="Stat Test"
|
|
133
|
+
value="300"
|
|
134
|
+
toggleBtn={<button>Toggle</button>}
|
|
135
|
+
statisticPosition="row-below"
|
|
136
|
+
statistic={<div data-testid="statistic">Stat</div>}
|
|
137
|
+
description="Description"
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
// Locate one of the texts rendered inside the statistic container.
|
|
141
|
+
const descElement = screen.getByText("Description");
|
|
142
|
+
// The description is inside the container whose class is built on line 59
|
|
143
|
+
const statisticContainer = descElement.closest("div");
|
|
144
|
+
expect(statisticContainer).toBeDefined();
|
|
145
|
+
// Expect the container to include the classes from statisticPosition 'row-below'
|
|
146
|
+
expect(statisticContainer?.className).toContain("gap-2");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should apply 'row-next' classes when statisticPosition is 'row-next' (Line 63)", () => {
|
|
150
|
+
render(
|
|
151
|
+
<Metrics
|
|
152
|
+
title="Stat Next Test"
|
|
153
|
+
value="400"
|
|
154
|
+
toggleBtn={<button>Toggle</button>}
|
|
155
|
+
statisticPosition="row-next"
|
|
156
|
+
statistic={<div data-testid="statistic">Stat</div>}
|
|
157
|
+
description="Description"
|
|
158
|
+
/>
|
|
159
|
+
);
|
|
160
|
+
const descElement = screen.getByText("Description");
|
|
161
|
+
const statisticContainer = descElement.closest("div");
|
|
162
|
+
expect(statisticContainer).toBeDefined();
|
|
163
|
+
// Expect classes from statisticPosition 'row-next'
|
|
164
|
+
expect(statisticContainer?.className).toContain("gap-2");
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export interface MetricProps {
|
|
4
|
+
title?: string;
|
|
5
|
+
value: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
icon?: React.ReactNode;
|
|
8
|
+
statistic?: React.ReactNode;
|
|
9
|
+
chartImg?: React.ReactNode;
|
|
10
|
+
chartPosition?: "side" | "bottom";
|
|
11
|
+
actionBtns?: React.ReactNode[];
|
|
12
|
+
toggleBtn: React.ReactNode;
|
|
13
|
+
iconPosition?: "topWithTitle" | "topAlone" | "iconSide" | "hidden";
|
|
14
|
+
statisticPosition?: "row-between" | "row-next" | "row-below";
|
|
15
|
+
dir?: "rtl" | "ltr";
|
|
16
|
+
iconType?: "success" | "notification" | "secondary";
|
|
17
|
+
titleType?: "normal" | "bold";
|
|
18
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
|
|
2
|
+
import { Card, CardAction, CardHeader } from "../ui/card";
|
|
3
|
+
import { MetricProps } from "./_.types";
|
|
4
|
+
import { baseStyles, iconStyles, titleStyle } from "./_.style";
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
export const Metrics = ({
|
|
9
|
+
title,
|
|
10
|
+
value,
|
|
11
|
+
description,
|
|
12
|
+
icon,
|
|
13
|
+
iconType = "secondary",
|
|
14
|
+
statistic,
|
|
15
|
+
chartImg,
|
|
16
|
+
chartPosition,
|
|
17
|
+
actionBtns,
|
|
18
|
+
toggleBtn,
|
|
19
|
+
iconPosition = "hidden",
|
|
20
|
+
statisticPosition,
|
|
21
|
+
titleType = "normal",
|
|
22
|
+
}: MetricProps) => {
|
|
23
|
+
return (
|
|
24
|
+
<Card className="flex flex-col gap-0 rounded-xl border border-rborder-secondary bg-rbg-primary shadow-xs w-full sm:max-w-[380px] max-w-[311px] h-fit justify-start p-0">
|
|
25
|
+
<div className="flex gap-5 w-full py-5 px-4 sm:p-6">
|
|
26
|
+
{iconPosition == "iconSide" && (
|
|
27
|
+
<div className={cn("self-center", iconStyles[iconType])}>{icon}</div>
|
|
28
|
+
)}
|
|
29
|
+
<div
|
|
30
|
+
className={cn(
|
|
31
|
+
baseStyles,
|
|
32
|
+
`${
|
|
33
|
+
(iconPosition == "topWithTitle" || iconPosition == "topAlone") &&
|
|
34
|
+
"gap-5 md:gap-6"
|
|
35
|
+
}`
|
|
36
|
+
)}
|
|
37
|
+
>
|
|
38
|
+
<CardHeader className="flex justify-between w-full px-0">
|
|
39
|
+
<div className="flex items-center gap-3 ">
|
|
40
|
+
{(iconPosition == "topWithTitle" ||
|
|
41
|
+
iconPosition == "topAlone") && (
|
|
42
|
+
<div className={iconStyles[iconType]}>{icon}</div>
|
|
43
|
+
)}
|
|
44
|
+
{iconPosition != "topAlone" && (
|
|
45
|
+
<span className={titleStyle[titleType]}>{title}</span>
|
|
46
|
+
)}
|
|
47
|
+
</div>
|
|
48
|
+
{toggleBtn}
|
|
49
|
+
</CardHeader>
|
|
50
|
+
<div className="flex flex-col w-full gap-2">
|
|
51
|
+
{iconPosition == "topAlone" && (
|
|
52
|
+
<span className={titleStyle[titleType]}>{title}</span>
|
|
53
|
+
)}
|
|
54
|
+
<div className={"w-full flex justify-between gap-4"}>
|
|
55
|
+
<div
|
|
56
|
+
className={cn(
|
|
57
|
+
"flex justify-between items-center w-full h-fit px-0",
|
|
58
|
+
`${
|
|
59
|
+
statisticPosition == "row-below" &&
|
|
60
|
+
"flex-col just-start items-start gap-4"
|
|
61
|
+
}`,
|
|
62
|
+
`${
|
|
63
|
+
statisticPosition == "row-next" &&
|
|
64
|
+
"flex justify-start items-start gap-2"
|
|
65
|
+
}`
|
|
66
|
+
)}
|
|
67
|
+
>
|
|
68
|
+
<span className="text-display-md font-normal text-rtext-primary-900">
|
|
69
|
+
{value}
|
|
70
|
+
</span>
|
|
71
|
+
<div className="flex gap-2 items-center">
|
|
72
|
+
{statistic}
|
|
73
|
+
{description && (
|
|
74
|
+
<span className="font-medium text-sm text-rtext-tertiary-600">
|
|
75
|
+
{description}
|
|
76
|
+
</span>
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
{chartPosition == "side" && (
|
|
81
|
+
<div className="w-full">{chartImg}</div>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
{chartPosition == "bottom" && (
|
|
86
|
+
<div className="w-full">{chartImg}</div>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
{actionBtns && (
|
|
91
|
+
<CardAction className="flex flex-row-reverse items-center justify-between py-4 px-6 border-t border-rborder-secondary gap-1.5 w-full">
|
|
92
|
+
{actionBtns?.map((btn, index) => (
|
|
93
|
+
<React.Fragment key={index}>{btn}</React.Fragment>
|
|
94
|
+
))}
|
|
95
|
+
</CardAction>
|
|
96
|
+
)}
|
|
97
|
+
</Card>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { Meta } from "@storybook/react-vite";
|
|
3
|
+
import { Modal } from ".";
|
|
4
|
+
import { modalVariantProps } from "./_.types";
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
title: "Components/Modal",
|
|
8
|
+
component: Modal,
|
|
9
|
+
// tags: ["autodocs"],
|
|
10
|
+
argTypes: {
|
|
11
|
+
size: {
|
|
12
|
+
control: { type: "radio", defaultValue: "md" },
|
|
13
|
+
options: ["sm", "md", "lg"],
|
|
14
|
+
},
|
|
15
|
+
overflow: {
|
|
16
|
+
control: { type: "radio", defaultValue: "auto" },
|
|
17
|
+
options: ["hidden", "auto"],
|
|
18
|
+
},
|
|
19
|
+
position: {
|
|
20
|
+
control: { type: "radio", defaultValue: "center" },
|
|
21
|
+
options: ["center", "bottom"],
|
|
22
|
+
},
|
|
23
|
+
showModal: {
|
|
24
|
+
control: { type: "boolean", defaultValue: true },
|
|
25
|
+
},
|
|
26
|
+
modalTitle: {
|
|
27
|
+
control: { type: "text" },
|
|
28
|
+
},
|
|
29
|
+
onClick: {
|
|
30
|
+
control: { action: "close" },
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
args: {
|
|
34
|
+
size: "md",
|
|
35
|
+
overflow: "auto",
|
|
36
|
+
position: "center",
|
|
37
|
+
modalTitle: "Main Modal",
|
|
38
|
+
showModal: true,
|
|
39
|
+
modalContentStyle: "gap-4",
|
|
40
|
+
},
|
|
41
|
+
} as Meta<typeof Modal>;
|
|
42
|
+
|
|
43
|
+
type Story = StoryObj<modalVariantProps>;
|
|
44
|
+
|
|
45
|
+
export const Default: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
children: "Modal Content",
|
|
48
|
+
overflow: "auto",
|
|
49
|
+
showModal: true,
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export const Center = {
|
|
54
|
+
args: {
|
|
55
|
+
position: "center",
|
|
56
|
+
children: "Modal Content",
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const Bottom = {
|
|
61
|
+
args: {
|
|
62
|
+
position: "bottom",
|
|
63
|
+
children: "Modal Content",
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const WithTitle = {
|
|
68
|
+
args: {
|
|
69
|
+
modalTitle: "Modal with Title",
|
|
70
|
+
children: "Modal Content",
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const WithCustomCloseIcon = {
|
|
75
|
+
args: {
|
|
76
|
+
closeIconStroke: "red",
|
|
77
|
+
children: "Modal Content",
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const Size = {
|
|
82
|
+
args: {
|
|
83
|
+
size: "sm",
|
|
84
|
+
children: "Modal Content",
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const CloseIcon = {
|
|
89
|
+
args: {
|
|
90
|
+
showCloseIcon: false,
|
|
91
|
+
children: "Modal Content",
|
|
92
|
+
},
|
|
93
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cva } from "class-variance-authority";
|
|
2
|
+
|
|
3
|
+
const baseStyles =
|
|
4
|
+
"modal-container fixed left-1/2 right-auto flex max-h-[80dvh] w-4/5 cursor-default flex-col rounded bg-white text-center";
|
|
5
|
+
|
|
6
|
+
const sizeStyle = {
|
|
7
|
+
sm: "max-w-rsm",
|
|
8
|
+
md: "max-w-rmd",
|
|
9
|
+
lg: "max-w-rlg",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const modalVariants = cva(
|
|
13
|
+
baseStyles,
|
|
14
|
+
{
|
|
15
|
+
variants: {
|
|
16
|
+
size: sizeStyle,
|
|
17
|
+
overflow: {
|
|
18
|
+
hidden: "overflow-hidden",
|
|
19
|
+
auto: "overflow-auto",
|
|
20
|
+
},
|
|
21
|
+
position: {
|
|
22
|
+
center: "top-1/2 -translate-y-1/2 animate-slide-in-center",
|
|
23
|
+
bottom: "bottom-0 animate-slide-in-bottom"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
defaultVariants: {
|
|
27
|
+
size: "md",
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { Modal } from ".";
|
|
4
|
+
|
|
5
|
+
// Mocking Radix Slot to make it render as span for testing purposes
|
|
6
|
+
vi.mock("@/icons/general/x-close", () => ({
|
|
7
|
+
default: () => <svg data-testid="x-close-icon" />,
|
|
8
|
+
}));
|
|
9
|
+
vi.mock("react-dom", async () => {
|
|
10
|
+
const actual = await vi.importActual<typeof import("react-dom")>("react-dom");
|
|
11
|
+
return {
|
|
12
|
+
...actual,
|
|
13
|
+
default: actual, // ✅ add default for compatibility with ESM
|
|
14
|
+
createPortal: (node: React.ReactNode) => node,
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
vi.mock("@/lib/zIndexUtils", () => ({
|
|
18
|
+
getBackdropZIndex: (depth: number) => 40 + depth * 10,
|
|
19
|
+
getModalZIndex: (depth: number) => 50 + depth * 10,
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
describe("Modal Component", () => {
|
|
23
|
+
const baseProps = {
|
|
24
|
+
showModal: true,
|
|
25
|
+
closeHandler: vi.fn(),
|
|
26
|
+
modalTitle: "Example Modal",
|
|
27
|
+
children: <div data-testid="modal-content">Test content</div>,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
document.body.style.overflow = "";
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
vi.clearAllMocks();
|
|
36
|
+
document.body.style.overflow = "";
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("renders modal correctly when showModal is true", () => {
|
|
40
|
+
render(<Modal {...baseProps} />);
|
|
41
|
+
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
|
42
|
+
expect(screen.getByText("Example Modal")).toBeInTheDocument();
|
|
43
|
+
expect(screen.getByTestId("modal-content")).toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("does not render modal when showModal is false", () => {
|
|
47
|
+
render(<Modal {...baseProps} showModal={false} />);
|
|
48
|
+
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("calls closeHandler on backdrop click", () => {
|
|
52
|
+
render(<Modal {...baseProps} />);
|
|
53
|
+
const backdrop = screen.getAllByRole("button");
|
|
54
|
+
fireEvent.click(backdrop[0]);
|
|
55
|
+
expect(baseProps.closeHandler).toHaveBeenCalled();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("calls closeHandler on close icon click", () => {
|
|
59
|
+
render(<Modal {...baseProps} />);
|
|
60
|
+
const closeButton = screen.getByTestId("x-close-icon");
|
|
61
|
+
fireEvent.click(closeButton);
|
|
62
|
+
expect(baseProps.closeHandler).toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("applies correct z-index from utility functions", () => {
|
|
66
|
+
render(<Modal {...baseProps} />);
|
|
67
|
+
const backdrop = screen.getAllByRole("button");
|
|
68
|
+
const dialog = screen.getByRole("dialog");
|
|
69
|
+
expect(backdrop[0]).toHaveStyle("z-index: 50");
|
|
70
|
+
expect(dialog).toHaveStyle("z-index: 60");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("sets and resets document body overflow to prevent scrolling", () => {
|
|
74
|
+
const { unmount } = render(<Modal {...baseProps} />);
|
|
75
|
+
expect(document.body.style.overflow).toBe("hidden");
|
|
76
|
+
unmount();
|
|
77
|
+
expect(document.body.style.overflow).toBe("");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("does not render close icon when showCloseIcon is false", () => {
|
|
81
|
+
render(<Modal {...baseProps} showCloseIcon={false} />);
|
|
82
|
+
expect(screen.queryByTestId("x-close-icon")).not.toBeInTheDocument();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("renders close icon when showCloseIcon is true", () => {
|
|
86
|
+
render(<Modal {...baseProps} showCloseIcon={true} />);
|
|
87
|
+
expect(screen.getByTestId("x-close-icon")).toBeInTheDocument();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type modalVariantProps = {
|
|
2
|
+
size?: "sm" | "md" | "lg";
|
|
3
|
+
overflow?: "hidden" | "auto";
|
|
4
|
+
closeHandler: () => void;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
modalTitle?: string;
|
|
7
|
+
closeIconStroke?: string;
|
|
8
|
+
position?: "center" | "bottom"
|
|
9
|
+
showModal: boolean,
|
|
10
|
+
icon?: React.ReactNode;
|
|
11
|
+
headerIconDir?: "flex-col" | "flex-row";
|
|
12
|
+
modalContentStyle?: string;
|
|
13
|
+
showCloseIcon?: boolean;
|
|
14
|
+
}
|