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,162 @@
|
|
|
1
|
+
|
|
2
|
+
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
|
3
|
+
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
import React, { useState } from "react";
|
|
6
|
+
import { AccordionProps } from "../Accordion/type";
|
|
7
|
+
|
|
8
|
+
function Accordion({
|
|
9
|
+
type = "single",
|
|
10
|
+
collapsible = false,
|
|
11
|
+
className,
|
|
12
|
+
children,
|
|
13
|
+
...props
|
|
14
|
+
}: Readonly<AccordionProps>) {
|
|
15
|
+
const [openValue, setOpenValue] = useState<string | string[]>(
|
|
16
|
+
type === "multiple" ? [] : ""
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
if (type === "multiple") {
|
|
20
|
+
return (
|
|
21
|
+
<AccordionPrimitive.Root
|
|
22
|
+
type="multiple"
|
|
23
|
+
className={className}
|
|
24
|
+
value={openValue as string[]}
|
|
25
|
+
onValueChange={(val: string[] | string) => setOpenValue(val)}
|
|
26
|
+
data-slot="accordion"
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
{React.Children.map(children, (child) =>
|
|
30
|
+
React.isValidElement(child)
|
|
31
|
+
? React.cloneElement(child as React.ReactElement<{ openValue?: string | string[] }>, {
|
|
32
|
+
openValue,
|
|
33
|
+
})
|
|
34
|
+
: child
|
|
35
|
+
)}
|
|
36
|
+
</AccordionPrimitive.Root>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<AccordionPrimitive.Root
|
|
42
|
+
type="single"
|
|
43
|
+
collapsible={collapsible}
|
|
44
|
+
className={className}
|
|
45
|
+
value={openValue as string}
|
|
46
|
+
onValueChange={(val: string | string[]) => setOpenValue(val)}
|
|
47
|
+
data-slot="accordion"
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
{React.Children.map(children, (child) =>
|
|
51
|
+
React.isValidElement(child)
|
|
52
|
+
? React.cloneElement(child as React.ReactElement<{ openValue?: string | string[] }>, {
|
|
53
|
+
...(child.props ?? {}),
|
|
54
|
+
openValue,
|
|
55
|
+
})
|
|
56
|
+
: child
|
|
57
|
+
)}
|
|
58
|
+
</AccordionPrimitive.Root>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
type AccordionItemProps = {
|
|
64
|
+
className?: string;
|
|
65
|
+
children?: React.ReactNode;
|
|
66
|
+
value: string;
|
|
67
|
+
openValue?: string | string[];
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
function AccordionItem({
|
|
71
|
+
className,
|
|
72
|
+
openValue,
|
|
73
|
+
value,
|
|
74
|
+
children,
|
|
75
|
+
...props
|
|
76
|
+
}: Readonly<AccordionItemProps>) {
|
|
77
|
+
return (
|
|
78
|
+
<AccordionPrimitive.Item
|
|
79
|
+
data-slot="accordion-item"
|
|
80
|
+
className={cn("border-b last:border-b-0", className)}
|
|
81
|
+
value={value}
|
|
82
|
+
{...props}
|
|
83
|
+
>
|
|
84
|
+
{React.Children.map(children, (child) =>
|
|
85
|
+
React.isValidElement(child)
|
|
86
|
+
? React.cloneElement(child as React.ReactElement<{ itemValue?: string; openValue?: string | string[] }>, {
|
|
87
|
+
itemValue: value,
|
|
88
|
+
openValue,
|
|
89
|
+
})
|
|
90
|
+
: child
|
|
91
|
+
)}
|
|
92
|
+
</AccordionPrimitive.Item>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface AccordionTriggerProps
|
|
97
|
+
extends React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> {
|
|
98
|
+
iconOpen?: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
|
99
|
+
iconClosed?: React.ComponentType<React.SVGProps<SVGSVGElement>>;
|
|
100
|
+
itemValue?: string;
|
|
101
|
+
openValue?: string | string[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function AccordionTrigger({
|
|
105
|
+
className,
|
|
106
|
+
children,
|
|
107
|
+
iconOpen: IconOpen = ChevronUpIcon,
|
|
108
|
+
iconClosed: IconClosed = ChevronDownIcon,
|
|
109
|
+
itemValue,
|
|
110
|
+
openValue,
|
|
111
|
+
...props
|
|
112
|
+
}: Readonly<AccordionTriggerProps>) {
|
|
113
|
+
const isOpen =
|
|
114
|
+
Array.isArray(openValue) && itemValue
|
|
115
|
+
? openValue.includes(itemValue)
|
|
116
|
+
: openValue === itemValue;
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<AccordionPrimitive.Header className="flex w-full">
|
|
120
|
+
<AccordionPrimitive.Trigger
|
|
121
|
+
data-slot="accordion-trigger"
|
|
122
|
+
className={cn(
|
|
123
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50",
|
|
124
|
+
className,
|
|
125
|
+
isOpen ? "rounded-t-md" : "rounded-md"
|
|
126
|
+
)}
|
|
127
|
+
{...props}
|
|
128
|
+
>
|
|
129
|
+
{children}
|
|
130
|
+
{isOpen ? (
|
|
131
|
+
<IconOpen className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
|
|
132
|
+
) : (
|
|
133
|
+
<IconClosed className="text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" />
|
|
134
|
+
)}
|
|
135
|
+
</AccordionPrimitive.Trigger>
|
|
136
|
+
</AccordionPrimitive.Header>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
interface AccordionContentProps
|
|
141
|
+
extends React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content> {
|
|
142
|
+
className?: string;
|
|
143
|
+
children?: React.ReactNode;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function AccordionContent({
|
|
147
|
+
className,
|
|
148
|
+
children,
|
|
149
|
+
...props
|
|
150
|
+
}: Readonly<AccordionContentProps>) {
|
|
151
|
+
return (
|
|
152
|
+
<AccordionPrimitive.Content
|
|
153
|
+
data-slot="accordion-content"
|
|
154
|
+
className="data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm"
|
|
155
|
+
{...props}
|
|
156
|
+
>
|
|
157
|
+
<div className={cn("pt-0 pb-4", className)}>{children}</div>
|
|
158
|
+
</AccordionPrimitive.Content>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { textSizeStyle } from "@/components/Avatar/_.style";
|
|
2
|
+
import { AvatarDescription as AvatarDescriptionType } from "@/components/Avatar/_.types";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
export const AvatarDescription = ({
|
|
6
|
+
title,
|
|
7
|
+
description,
|
|
8
|
+
size,
|
|
9
|
+
}: AvatarDescriptionType) => {
|
|
10
|
+
return (
|
|
11
|
+
<div className="flex flex-col">
|
|
12
|
+
<span
|
|
13
|
+
className={cn(
|
|
14
|
+
`text-rtext-secondary-700 font-semibold`,
|
|
15
|
+
textSizeStyle[size]
|
|
16
|
+
)}
|
|
17
|
+
>
|
|
18
|
+
{title}
|
|
19
|
+
</span>
|
|
20
|
+
<span
|
|
21
|
+
className={cn(
|
|
22
|
+
`font-normal text-rtext-tertiary-600`,
|
|
23
|
+
textSizeStyle[size]
|
|
24
|
+
)}
|
|
25
|
+
>
|
|
26
|
+
{description}
|
|
27
|
+
</span>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { sizeStyles } from "@/components/Avatar/_.style";
|
|
2
|
+
import { AvatarGroupProps } from "@/components/Avatar/_.types";
|
|
3
|
+
import { TooltipWrapper } from "@/components/Tooltip";
|
|
4
|
+
import Plus from "@/icons/general/plus";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
import React from "react";
|
|
7
|
+
|
|
8
|
+
export const AvatarGroup: React.FC<AvatarGroupProps> = ({
|
|
9
|
+
imgList,
|
|
10
|
+
size,
|
|
11
|
+
showAmount = 5,
|
|
12
|
+
showAddBtn = false,
|
|
13
|
+
tooltip,
|
|
14
|
+
addBtnAction,
|
|
15
|
+
}) => {
|
|
16
|
+
const visibleAvatars = imgList.slice(0, showAmount);
|
|
17
|
+
const extraCount = imgList.length - showAmount;
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex -space-x-1 w-full">
|
|
20
|
+
{visibleAvatars.map((img, index) => (
|
|
21
|
+
<img
|
|
22
|
+
key={index}
|
|
23
|
+
alt=""
|
|
24
|
+
className={cn(
|
|
25
|
+
"inline-block rounded-full ring-2 ring-white",
|
|
26
|
+
sizeStyles[size]
|
|
27
|
+
)}
|
|
28
|
+
style={{ zIndex: index }}
|
|
29
|
+
{...img}
|
|
30
|
+
/>
|
|
31
|
+
))}
|
|
32
|
+
{extraCount > 0 && (
|
|
33
|
+
<div
|
|
34
|
+
className={cn(
|
|
35
|
+
"inline-flex items-center justify-center rounded-full bg-gray-100 border-2 border-white font-medium text-gray-700 shadow-2xs align-middle hover:bg-gray-200 focus:outline-hidden focus:bg-gray-300 text-sm dark:bg-neutral-700 dark:text-white dark:hover:bg-neutral-600 dark:focus:bg-neutral-600 dark:border-neutral-800",
|
|
36
|
+
sizeStyles[size]
|
|
37
|
+
)}
|
|
38
|
+
style={{ zIndex: showAmount }}
|
|
39
|
+
>
|
|
40
|
+
<span className="font-medium text-xs">+{extraCount}</span>
|
|
41
|
+
</div>
|
|
42
|
+
)}
|
|
43
|
+
{showAddBtn && tooltip ? (
|
|
44
|
+
<TooltipWrapper {...tooltip}>
|
|
45
|
+
<button
|
|
46
|
+
className={cn(
|
|
47
|
+
`mx-2 p-1 flex justify-center items-center cursor-pointer stroke-rfg-quinary-400 rounded-full bg-rbg-primary border border-dashed border-rborder-primary hover:bg-rbg-primary-hover disabled:bg-rbg-disabled-subtle disabled:cur`,
|
|
48
|
+
sizeStyles[size]
|
|
49
|
+
)}
|
|
50
|
+
onClick={addBtnAction}
|
|
51
|
+
>
|
|
52
|
+
<Plus />
|
|
53
|
+
</button>
|
|
54
|
+
</TooltipWrapper>
|
|
55
|
+
) : (
|
|
56
|
+
<button
|
|
57
|
+
className={cn(
|
|
58
|
+
`mx-2 p-1 flex justify-center items-center cursor-pointer stroke-rfg-quinary-400 rounded-full bg-rbg-primary border border-dashed border-rborder-primary hover:bg-rbg-primary-hover disabled:bg-rbg-disabled-subtle disabled:cur`,
|
|
59
|
+
sizeStyles[size]
|
|
60
|
+
)}
|
|
61
|
+
onClick={addBtnAction}
|
|
62
|
+
>
|
|
63
|
+
<Plus />
|
|
64
|
+
</button>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {
|
|
2
|
+
dotSizeStyle,
|
|
3
|
+
iconSizeStyle,
|
|
4
|
+
sizeStyles,
|
|
5
|
+
} from "@/components/Avatar/_.style";
|
|
6
|
+
import { SingleAvatarProps } from "@/components/Avatar/_.types";
|
|
7
|
+
import { cn } from "@/lib/utils";
|
|
8
|
+
import React from "react";
|
|
9
|
+
|
|
10
|
+
export const AvatarSingle: React.FC<SingleAvatarProps> = ({
|
|
11
|
+
img,
|
|
12
|
+
size,
|
|
13
|
+
notifyColor = "",
|
|
14
|
+
notifyIcon = "",
|
|
15
|
+
iconBorder,
|
|
16
|
+
}) => {
|
|
17
|
+
return (
|
|
18
|
+
<div className={cn(`relative inline-block`, sizeStyles[size])}>
|
|
19
|
+
<img
|
|
20
|
+
alt=""
|
|
21
|
+
{...img}
|
|
22
|
+
className={cn(
|
|
23
|
+
`rounded-full object-cover ring-2 ring-white`,
|
|
24
|
+
sizeStyles[size]
|
|
25
|
+
)}
|
|
26
|
+
/>
|
|
27
|
+
{notifyColor && (
|
|
28
|
+
<span
|
|
29
|
+
className={cn(
|
|
30
|
+
`absolute bottom-0 right-1 block rounded-full ${notifyColor} ring-2 ring-white`,
|
|
31
|
+
dotSizeStyle[size]
|
|
32
|
+
)}
|
|
33
|
+
/>
|
|
34
|
+
)}
|
|
35
|
+
{notifyIcon && (
|
|
36
|
+
<span
|
|
37
|
+
className={cn(
|
|
38
|
+
`absolute bottom-0 right-1 block rounded-full`,
|
|
39
|
+
iconSizeStyle[size],
|
|
40
|
+
`has-[>svg]:${iconSizeStyle[size]} ${
|
|
41
|
+
iconBorder && "ring-2 ring-white"
|
|
42
|
+
}`
|
|
43
|
+
)}
|
|
44
|
+
>
|
|
45
|
+
{notifyIcon}
|
|
46
|
+
</span>
|
|
47
|
+
)}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="card"
|
|
9
|
+
className={cn(
|
|
10
|
+
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
|
11
|
+
className
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
data-slot="card-header"
|
|
22
|
+
className={cn(
|
|
23
|
+
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
|
24
|
+
className
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
data-slot="card-title"
|
|
35
|
+
className={cn("leading-none font-semibold", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
data-slot="card-description"
|
|
45
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
data-slot="card-action"
|
|
55
|
+
className={cn(
|
|
56
|
+
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
57
|
+
className
|
|
58
|
+
)}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
65
|
+
return (
|
|
66
|
+
<div
|
|
67
|
+
data-slot="card-content"
|
|
68
|
+
className={cn("px-6", className)}
|
|
69
|
+
{...props}
|
|
70
|
+
/>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
data-slot="card-footer"
|
|
78
|
+
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export {
|
|
85
|
+
Card,
|
|
86
|
+
CardHeader,
|
|
87
|
+
CardFooter,
|
|
88
|
+
CardTitle,
|
|
89
|
+
CardAction,
|
|
90
|
+
CardDescription,
|
|
91
|
+
CardContent,
|
|
92
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
2
|
+
import "@testing-library/jest-dom";
|
|
3
|
+
import { describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { PlanCardBasic } from ".";
|
|
5
|
+
import { iconSizeStyle } from "@/components/CheckboxGroup/PlanCard/_.style";
|
|
6
|
+
|
|
7
|
+
describe("PlanCardBasic", () => {
|
|
8
|
+
const baseProps = {
|
|
9
|
+
per: "month",
|
|
10
|
+
title: "Basic Plan",
|
|
11
|
+
description: "This is a test plan",
|
|
12
|
+
price: "$10",
|
|
13
|
+
id: "plan-1",
|
|
14
|
+
checkboxIndex: 0,
|
|
15
|
+
name: "plan",
|
|
16
|
+
value: "basic",
|
|
17
|
+
onChange: vi.fn(),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
it("renders title, price, per, and description", () => {
|
|
21
|
+
render(<PlanCardBasic {...baseProps} />);
|
|
22
|
+
|
|
23
|
+
expect(screen.getByText("Basic Plan")).toBeInTheDocument();
|
|
24
|
+
expect(screen.getByText("$10/month")).toBeInTheDocument();
|
|
25
|
+
expect(screen.getByText("This is a test plan")).toBeInTheDocument();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("renders IconWrapper and Layer with correct size", () => {
|
|
29
|
+
render(<PlanCardBasic {...baseProps} size="md" />);
|
|
30
|
+
const iconWrapper = screen.getByTestId("icon-wrapper");
|
|
31
|
+
expect(iconWrapper).toBeInTheDocument();
|
|
32
|
+
const svg = iconWrapper.querySelector("svg");
|
|
33
|
+
expect(svg).toHaveAttribute("width", iconSizeStyle["md"]);
|
|
34
|
+
expect(svg).toHaveAttribute("height", iconSizeStyle["md"]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("renders a radio input when type is 'radio'", () => {
|
|
38
|
+
render(<PlanCardBasic {...baseProps} type="radio" />);
|
|
39
|
+
const radio = screen.getByRole("radio");
|
|
40
|
+
expect(radio).toBeInTheDocument();
|
|
41
|
+
fireEvent.click(radio);
|
|
42
|
+
expect(baseProps.onChange).toHaveBeenCalled();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("renders a checkbox input when type is not 'radio'", () => {
|
|
46
|
+
render(<PlanCardBasic {...baseProps} type="checkbox" />);
|
|
47
|
+
const checkbox = screen.getByRole("checkbox");
|
|
48
|
+
expect(checkbox).toBeInTheDocument();
|
|
49
|
+
fireEvent.click(checkbox);
|
|
50
|
+
expect(baseProps.onChange).toHaveBeenCalled();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("renders disabled radio input correctly", () => {
|
|
54
|
+
render(<PlanCardBasic {...baseProps} type="radio" disabled checked />);
|
|
55
|
+
const radio = screen.getByRole("radio") as HTMLInputElement;
|
|
56
|
+
expect(radio).toBeDisabled();
|
|
57
|
+
expect(radio.checked).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("renders disabled checkbox input correctly", () => {
|
|
61
|
+
render(<PlanCardBasic {...baseProps} type="checkbox" disabled checked />);
|
|
62
|
+
const checkbox = screen.getByRole("checkbox") as HTMLInputElement;
|
|
63
|
+
expect(checkbox).toBeDisabled();
|
|
64
|
+
expect(checkbox.checked).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Checkbox } from "@/components/Checkbox";
|
|
2
|
+
import { iconSizeStyle } from "@/components/CheckboxGroup/PlanCard/_.style";
|
|
3
|
+
import { PlanCardProps } from "@/components/CheckboxGroup/PlanCard/_.types";
|
|
4
|
+
import { Radio } from "@/components/Radio";
|
|
5
|
+
import Layer from "@/icons/general/layer";
|
|
6
|
+
import IconWrapper from "../../../icon-wrapper";
|
|
7
|
+
|
|
8
|
+
export function PlanCardBasic({
|
|
9
|
+
per,
|
|
10
|
+
size = "sm",
|
|
11
|
+
title = "",
|
|
12
|
+
description,
|
|
13
|
+
price,
|
|
14
|
+
id,
|
|
15
|
+
checkboxIndex,
|
|
16
|
+
checked,
|
|
17
|
+
name = "",
|
|
18
|
+
value = "",
|
|
19
|
+
inputProps,
|
|
20
|
+
onChange,
|
|
21
|
+
type,
|
|
22
|
+
onCardClick,
|
|
23
|
+
disabled,
|
|
24
|
+
}: Readonly<PlanCardProps>) {
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
<IconWrapper data-testid="icon-wrapper">
|
|
28
|
+
<Layer width={iconSizeStyle[size]} height={iconSizeStyle[size]} />
|
|
29
|
+
</IconWrapper>
|
|
30
|
+
<div onClick={onCardClick} className="flex flex-col grow justify-between">
|
|
31
|
+
<div className="flex">
|
|
32
|
+
<h3 className="font-medium text-foreground text-rtext-secondary-700">
|
|
33
|
+
{title}
|
|
34
|
+
</h3>
|
|
35
|
+
|
|
36
|
+
<span>
|
|
37
|
+
{price}/{per}
|
|
38
|
+
</span>
|
|
39
|
+
</div>
|
|
40
|
+
<div className="text-muted-foreground">{description}</div>
|
|
41
|
+
</div>
|
|
42
|
+
<div className="flex items-start gap-2">
|
|
43
|
+
{type === "radio" ? (
|
|
44
|
+
<Radio
|
|
45
|
+
onChange={onChange}
|
|
46
|
+
checked={checked}
|
|
47
|
+
disabled={disabled}
|
|
48
|
+
value={value}
|
|
49
|
+
name={name}
|
|
50
|
+
size={size}
|
|
51
|
+
id={id}
|
|
52
|
+
{...inputProps}
|
|
53
|
+
/>
|
|
54
|
+
) : (
|
|
55
|
+
<Checkbox
|
|
56
|
+
{...inputProps}
|
|
57
|
+
disabled={disabled}
|
|
58
|
+
id={id}
|
|
59
|
+
name={name}
|
|
60
|
+
onChange={onChange}
|
|
61
|
+
size={size}
|
|
62
|
+
value={value}
|
|
63
|
+
checkboxIndex={checkboxIndex}
|
|
64
|
+
checked={checked}
|
|
65
|
+
/>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
</>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
2
|
+
import "@testing-library/jest-dom";
|
|
3
|
+
import { describe, it, expect, vi } from "vitest";
|
|
4
|
+
import { ReactNode } from "react";
|
|
5
|
+
import { PlanCardWithHeader } from ".";
|
|
6
|
+
|
|
7
|
+
// Mock the Header and Badge components to avoid actual component logic
|
|
8
|
+
vi.mock("./header", () => ({
|
|
9
|
+
Header: ({
|
|
10
|
+
title,
|
|
11
|
+
price,
|
|
12
|
+
onChange,
|
|
13
|
+
}: {
|
|
14
|
+
title: string;
|
|
15
|
+
price: string;
|
|
16
|
+
onChange?: () => void; // required
|
|
17
|
+
}) => (
|
|
18
|
+
<div
|
|
19
|
+
data-testid="header"
|
|
20
|
+
onClick={onChange} // Mock the onClick event to trigger onChange
|
|
21
|
+
>
|
|
22
|
+
{title} - {price}
|
|
23
|
+
</div>
|
|
24
|
+
),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
vi.mock("@/components/Badge", () => ({
|
|
28
|
+
Badge: ({ children }: { children: ReactNode }) => (
|
|
29
|
+
<div data-testid="badge">{children}</div>
|
|
30
|
+
),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
describe("PlanCardWithHeader", () => {
|
|
34
|
+
it("calls onChange when input is checked or unchecked", () => {
|
|
35
|
+
const onChange = vi.fn();
|
|
36
|
+
|
|
37
|
+
// Render PlanCardWithHeader with a mock onChange handler
|
|
38
|
+
render(
|
|
39
|
+
<PlanCardWithHeader
|
|
40
|
+
title="Basic Plan"
|
|
41
|
+
price="$99"
|
|
42
|
+
per="month"
|
|
43
|
+
description="Plan description"
|
|
44
|
+
onChange={onChange}
|
|
45
|
+
checked={false}
|
|
46
|
+
size={"sm"}
|
|
47
|
+
checkboxIndex={1}
|
|
48
|
+
id={"name"}
|
|
49
|
+
value={"name2"}
|
|
50
|
+
name={"group"}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Find the header element that triggers the change
|
|
55
|
+
const header = screen.getByTestId("header");
|
|
56
|
+
|
|
57
|
+
// Trigger the click event on the header
|
|
58
|
+
fireEvent.click(header);
|
|
59
|
+
|
|
60
|
+
// Assert that onChange was called when the header is clicked
|
|
61
|
+
expect(onChange).toHaveBeenCalled();
|
|
62
|
+
});
|
|
63
|
+
it("renders Badge with the correct content and styles when badgeContent is provided", () => {
|
|
64
|
+
const badgeContent = "New!";
|
|
65
|
+
|
|
66
|
+
render(
|
|
67
|
+
<PlanCardWithHeader
|
|
68
|
+
title="Basic Plan"
|
|
69
|
+
price="$99"
|
|
70
|
+
per="month"
|
|
71
|
+
description="Plan description"
|
|
72
|
+
badgeContent={badgeContent}
|
|
73
|
+
checked={false}
|
|
74
|
+
size="sm"
|
|
75
|
+
checkboxIndex={1}
|
|
76
|
+
id="name"
|
|
77
|
+
value="name2"
|
|
78
|
+
name="group"
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Check if Badge is rendered
|
|
83
|
+
const badge = screen.getByText("New!");
|
|
84
|
+
expect(badge).toBeInTheDocument();
|
|
85
|
+
|
|
86
|
+
// Check if Badge contains the correct content (badgeContent)
|
|
87
|
+
expect(badge).toHaveTextContent(badgeContent);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("does not render Badge when badgeContent is not provided", () => {
|
|
91
|
+
render(
|
|
92
|
+
<PlanCardWithHeader
|
|
93
|
+
title="Basic Plan"
|
|
94
|
+
price="$99"
|
|
95
|
+
per="month"
|
|
96
|
+
description="Plan description"
|
|
97
|
+
checked={false}
|
|
98
|
+
size="sm"
|
|
99
|
+
checkboxIndex={1}
|
|
100
|
+
id="name"
|
|
101
|
+
value="name2"
|
|
102
|
+
name="group"
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Check if Badge is not rendered when no badgeContent is passed
|
|
107
|
+
const badge = screen.queryByTestId("badge");
|
|
108
|
+
expect(badge).toBeNull();
|
|
109
|
+
});
|
|
110
|
+
});
|