@buerokratt-ria/common-gui-components 0.0.1

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 (122) hide show
  1. package/.eslintrc.json +18 -0
  2. package/CHANGELOG.md +7 -0
  3. package/MAKING_CHANGES.md +8 -0
  4. package/README.md +49 -0
  5. package/assets/ding.mp3 +0 -0
  6. package/assets/logo-white.svg +29 -0
  7. package/assets/logo.svg +31 -0
  8. package/assets/newMessageSound.mp3 +0 -0
  9. package/constants/config.ts +12 -0
  10. package/constants/index.ts +1 -0
  11. package/context/index.ts +1 -0
  12. package/context/toastContext.tsx +60 -0
  13. package/hooks/index.ts +3 -0
  14. package/hooks/useAudio.tsx +30 -0
  15. package/hooks/useDocumentEscapeListener.tsx +17 -0
  16. package/hooks/useToast.tsx +5 -0
  17. package/i18n.ts +26 -0
  18. package/index.ts +6 -0
  19. package/package.json +122 -0
  20. package/project.json +52 -0
  21. package/services/api.ts +74 -0
  22. package/services/index.ts +3 -0
  23. package/services/sse-service.ts +30 -0
  24. package/services/users.ts +58 -0
  25. package/store/index.ts +253 -0
  26. package/templates/history-page/index.ts +1 -0
  27. package/templates/history-page/src/History.scss +47 -0
  28. package/templates/history-page/src/index.tsx +998 -0
  29. package/templates/history-page/src/unfiyDate.tsx +7 -0
  30. package/translations/en/common.json +467 -0
  31. package/translations/et/common.json +467 -0
  32. package/tsconfig.base.json +21 -0
  33. package/tsconfig.json +17 -0
  34. package/tsconfig.spec.json +19 -0
  35. package/types/authorities.ts +8 -0
  36. package/types/botConfig.ts +7 -0
  37. package/types/chat.ts +126 -0
  38. package/types/customerSupportActivity.ts +5 -0
  39. package/types/deleteChatSettings.ts +9 -0
  40. package/types/emergencyNotice.ts +10 -0
  41. package/types/establishment.ts +4 -0
  42. package/types/index.ts +18 -0
  43. package/types/mainNavigation.ts +11 -0
  44. package/types/message.ts +74 -0
  45. package/types/organizationWorkingTime.ts +27 -0
  46. package/types/router.ts +4 -0
  47. package/types/service.ts +6 -0
  48. package/types/session.ts +7 -0
  49. package/types/skmConfig.ts +8 -0
  50. package/types/user.ts +40 -0
  51. package/types/userInfo.ts +16 -0
  52. package/types/userProfileSettings.ts +10 -0
  53. package/types/widgetConfig.ts +8 -0
  54. package/ui-components/Button/Button.scss +150 -0
  55. package/ui-components/Button/index.tsx +41 -0
  56. package/ui-components/ButtonMessage/ButtonMessage.scss +16 -0
  57. package/ui-components/ButtonMessage/index.tsx +19 -0
  58. package/ui-components/Card/Card.scss +69 -0
  59. package/ui-components/Card/index.tsx +39 -0
  60. package/ui-components/Chat/Chat.scss +447 -0
  61. package/ui-components/Chat/ChatMessage.tsx +270 -0
  62. package/ui-components/Chat/ChatTextArea.scss +110 -0
  63. package/ui-components/Chat/ChatTextArea.tsx +97 -0
  64. package/ui-components/Chat/LoaderOverlay.tsx +39 -0
  65. package/ui-components/Chat/Markdownify.tsx +49 -0
  66. package/ui-components/Chat/PreviewMessage.tsx +39 -0
  67. package/ui-components/Chat/Typing.scss +46 -0
  68. package/ui-components/Chat/index.tsx +1111 -0
  69. package/ui-components/ChatEvent/Chat.scss +40 -0
  70. package/ui-components/ChatEvent/index.tsx +216 -0
  71. package/ui-components/DataTable/CloseIcon.tsx +22 -0
  72. package/ui-components/DataTable/DataTable.scss +188 -0
  73. package/ui-components/DataTable/DeboucedInput.scss +11 -0
  74. package/ui-components/DataTable/DebouncedInput.tsx +54 -0
  75. package/ui-components/DataTable/Filter.tsx +121 -0
  76. package/ui-components/DataTable/index.tsx +432 -0
  77. package/ui-components/Dialog/Dialog.scss +63 -0
  78. package/ui-components/Dialog/index.tsx +44 -0
  79. package/ui-components/Drawer/Drawer.scss +40 -0
  80. package/ui-components/Drawer/index.tsx +42 -0
  81. package/ui-components/FormElements/FormCheckbox/FormCheckbox.scss +57 -0
  82. package/ui-components/FormElements/FormCheckbox/index.tsx +39 -0
  83. package/ui-components/FormElements/FormCheckboxes/FormCheckboxes.scss +63 -0
  84. package/ui-components/FormElements/FormCheckboxes/index.tsx +44 -0
  85. package/ui-components/FormElements/FormDatepicker/FormDatepicker.scss +154 -0
  86. package/ui-components/FormElements/FormDatepicker/index.tsx +123 -0
  87. package/ui-components/FormElements/FormInput/FormInput.scss +90 -0
  88. package/ui-components/FormElements/FormInput/index.tsx +47 -0
  89. package/ui-components/FormElements/FormRadios/FormRadios.scss +72 -0
  90. package/ui-components/FormElements/FormRadios/index.tsx +36 -0
  91. package/ui-components/FormElements/FormSelect/FormMultiselect.tsx +124 -0
  92. package/ui-components/FormElements/FormSelect/FormSelect.scss +121 -0
  93. package/ui-components/FormElements/FormSelect/index.tsx +100 -0
  94. package/ui-components/FormElements/FormTextarea/FormTextarea.scss +109 -0
  95. package/ui-components/FormElements/FormTextarea/index.tsx +154 -0
  96. package/ui-components/FormElements/Switch/Switch.scss +69 -0
  97. package/ui-components/FormElements/Switch/index.tsx +65 -0
  98. package/ui-components/FormElements/SwitchBox/SwitchBox.scss +45 -0
  99. package/ui-components/FormElements/SwitchBox/index.tsx +44 -0
  100. package/ui-components/FormElements/index.tsx +23 -0
  101. package/ui-components/HistoricalChat/ChatMessage.tsx +67 -0
  102. package/ui-components/HistoricalChat/HistoricalChat.scss +225 -0
  103. package/ui-components/HistoricalChat/index.tsx +282 -0
  104. package/ui-components/Icon/Icon.scss +17 -0
  105. package/ui-components/Icon/index.tsx +26 -0
  106. package/ui-components/Label/Label.scss +76 -0
  107. package/ui-components/Label/index.tsx +40 -0
  108. package/ui-components/OptionMessage/OptionMessage.scss +16 -0
  109. package/ui-components/OptionMessage/index.tsx +16 -0
  110. package/ui-components/Toast/Toast.scss +73 -0
  111. package/ui-components/Toast/index.tsx +54 -0
  112. package/ui-components/Tooltip/Tooltip.scss +17 -0
  113. package/ui-components/Tooltip/index.tsx +28 -0
  114. package/ui-components/Track/index.tsx +57 -0
  115. package/ui-components/index.tsx +53 -0
  116. package/utils/constants.ts +19 -0
  117. package/utils/format-bytes.ts +8 -0
  118. package/utils/generateUEID.ts +8 -0
  119. package/utils/local-storage-utils.ts +17 -0
  120. package/utils/parse-utils.ts +23 -0
  121. package/utils/state-management-utils.ts +13 -0
  122. package/vite.config.ts +67 -0
@@ -0,0 +1,282 @@
1
+ import React, { FC, useEffect, useRef, useState } from "react";
2
+ import { useTranslation } from "react-i18next";
3
+ import clsx from "clsx";
4
+ import { MdOutlineModeEditOutline, MdOutlineSave } from "react-icons/md";
5
+
6
+ import { Button, FormSelect, FormTextarea, Icon, Track } from "../";
7
+ import { ReactComponent as BykLogoWhite } from "../../assets/logo-white.svg";
8
+ import { CHAT_EVENTS, Chat as ChatType, BACKOFFICE_NAME } from "../../types/chat";
9
+ import { Message } from "../../types/message";
10
+ import ChatMessage from "./ChatMessage";
11
+ import "./HistoricalChat.scss";
12
+ import { apiDev } from "../../services/api";
13
+ import ChatEvent from "../../ui-components/ChatEvent";
14
+ import { ToastContextType } from "../../context";
15
+
16
+ type ChatProps = {
17
+ chat: ChatType;
18
+ header_link?: string;
19
+ trigger: boolean;
20
+ onChatStatusChange: (event: string) => void;
21
+ onCommentChange: (comment: string) => void;
22
+ selectedStatus: string | null;
23
+ showComment?: boolean;
24
+ showStatus?: boolean;
25
+ toastContext: ToastContextType | null;
26
+ onMessageClick?: (message: Message) => void;
27
+ };
28
+
29
+ type GroupedMessage = {
30
+ name: string;
31
+ title: string;
32
+ type: string;
33
+ messages: Message[];
34
+ };
35
+
36
+ const chatStatuses = [
37
+ CHAT_EVENTS.ACCEPTED,
38
+ CHAT_EVENTS.CLIENT_LEFT_FOR_UNKNOWN_REASONS,
39
+ CHAT_EVENTS.CLIENT_LEFT_WITH_ACCEPTED,
40
+ CHAT_EVENTS.CLIENT_LEFT_WITH_NO_RESOLUTION,
41
+ CHAT_EVENTS.HATE_SPEECH,
42
+ CHAT_EVENTS.OTHER,
43
+ CHAT_EVENTS.RESPONSE_SENT_TO_CLIENT_EMAIL,
44
+ ];
45
+
46
+ const HistoricalChat: FC<ChatProps> = ({
47
+ chat,
48
+ header_link,
49
+ trigger,
50
+ selectedStatus,
51
+ onChatStatusChange,
52
+ onCommentChange,
53
+ showComment = true,
54
+ showStatus = true,
55
+ toastContext,
56
+ onMessageClick,
57
+ }) => {
58
+ const { t } = useTranslation();
59
+ const chatRef = useRef<HTMLDivElement>(null);
60
+ const [messageGroups, setMessageGroups] = useState<GroupedMessage[]>([]);
61
+ const [editingComment, setEditingComment] = useState<string | null>(null);
62
+ const [messagesList, setMessagesList] = useState<Message[]>([]);
63
+ const [status, setStatus] = useState<string | null>(selectedStatus ?? null);
64
+ const [lastMessage, setLastMessage] = useState<Message>();
65
+ const [statuses, setStatuses] = useState(chatStatuses);
66
+
67
+ useEffect(() => {
68
+ getMessages();
69
+ }, [trigger]);
70
+
71
+ useEffect(() => {
72
+ const initializeComponent = () => {
73
+ setMessageGroups([]);
74
+ setMessagesList([]);
75
+ setLastMessage(undefined);
76
+ setStatuses(chatStatuses);
77
+ getMessages();
78
+ };
79
+
80
+ initializeComponent();
81
+ }, [chat]);
82
+
83
+ const getMessages = async () => {
84
+ if (!chat.id) return;
85
+ const { data: res } = await apiDev.post("agents/messages-by-id", {
86
+ chatId: chat.id,
87
+ });
88
+ setMessagesList(res.response);
89
+ };
90
+
91
+ const endUserFullName =
92
+ chat.endUserFirstName && chat.endUserLastName && chat.endUserFirstName !== "" && chat.endUserLastName !== ""
93
+ ? `${chat.endUserFirstName} ${chat.endUserLastName}`
94
+ : t("global.anonymous");
95
+
96
+ useEffect(() => {
97
+ setStatus(selectedStatus);
98
+ }, [selectedStatus, status]);
99
+
100
+ useEffect(() => {
101
+ if (!messagesList) return;
102
+ let groupedMessages: GroupedMessage[] = [];
103
+ messagesList.forEach((message) => {
104
+ const lastGroup = groupedMessages[groupedMessages.length - 1];
105
+ if (lastGroup?.type === message.authorRole) {
106
+ if (
107
+ !message.event ||
108
+ message.event.toLowerCase() === CHAT_EVENTS.GREETING ||
109
+ message.event.toLowerCase() === CHAT_EVENTS.WAITING_VALIDATION ||
110
+ message.event.toLowerCase() === CHAT_EVENTS.APPROVED_VALIDATION
111
+ ) {
112
+ lastGroup.messages.push({
113
+ ...message,
114
+ content:
115
+ message.event === CHAT_EVENTS.WAITING_VALIDATION
116
+ ? t("chat.waiting_validation").toString()
117
+ : message.content,
118
+ });
119
+ } else {
120
+ groupedMessages.push({
121
+ name: "",
122
+ type: "event",
123
+ title: "",
124
+ messages: [{ ...message }],
125
+ });
126
+ }
127
+ } else {
128
+ const isBackOfficeUser =
129
+ message.authorRole === "backoffice-user"
130
+ ? `${message.authorFirstName} ${message.authorLastName}`
131
+ : BACKOFFICE_NAME.DEFAULT;
132
+ groupedMessages.push({
133
+ name: message.authorRole === "end-user" ? endUserFullName : isBackOfficeUser,
134
+ type: message.authorRole,
135
+ title: message.csaTitle ?? "",
136
+ messages: [{ ...message }],
137
+ });
138
+ }
139
+ });
140
+
141
+ setMessageGroups(groupedMessages);
142
+ const lastMessage = messagesList[messagesList.length - 1];
143
+ if (
144
+ lastMessage?.event?.toLowerCase() === CHAT_EVENTS.CLIENT_LEFT_FOR_UNKNOWN_REASONS ||
145
+ lastMessage?.event?.toLowerCase() === CHAT_EVENTS.HATE_SPEECH ||
146
+ lastMessage?.event?.toLowerCase() === CHAT_EVENTS.OTHER ||
147
+ lastMessage?.event?.toLowerCase() === CHAT_EVENTS.RESPONSE_SENT_TO_CLIENT_EMAIL
148
+ ) {
149
+ setStatuses([CHAT_EVENTS.HATE_SPEECH, CHAT_EVENTS.OTHER, CHAT_EVENTS.RESPONSE_SENT_TO_CLIENT_EMAIL]);
150
+ } else if (
151
+ lastMessage?.event?.toLowerCase() === CHAT_EVENTS.CLIENT_LEFT_WITH_ACCEPTED ||
152
+ lastMessage?.event?.toLowerCase() === CHAT_EVENTS.CLIENT_LEFT_WITH_NO_RESOLUTION
153
+ ) {
154
+ setStatuses([]);
155
+ } else {
156
+ setStatuses(chatStatuses);
157
+ }
158
+ setLastMessage(lastMessage);
159
+ }, [messagesList, endUserFullName]);
160
+
161
+ useEffect(() => {
162
+ if (!chatRef.current || !messageGroups) return;
163
+ chatRef.current.scrollIntoView({ block: "end", inline: "end" });
164
+ }, [messageGroups]);
165
+
166
+ const isEvent = (group: GroupedMessage) => {
167
+ return (
168
+ group.type === "event" || group.name.trim() === "" || (!group.messages[0].content && group.messages[0].event)
169
+ );
170
+ };
171
+
172
+ return (
173
+ <div className="historical-chat">
174
+ <div className="historical-chat__body">
175
+ {header_link && <div className={"header-link"}>{header_link}</div>}
176
+ <div className="historical-chat__group-wrapper">
177
+ {messageGroups?.map((group, index) => (
178
+ <div
179
+ className={clsx(["historical-chat__group", `historical-chat__group--${group.type}`])}
180
+ key={`${group.name}-${index}`}
181
+ >
182
+ {isEvent(group) ? (
183
+ <ChatEvent message={group.messages[0]} />
184
+ ) : (
185
+ <>
186
+ <div className="historical-chat__group-initials">
187
+ {group.type === "buerokratt" || group.type === "chatbot" ? (
188
+ <BykLogoWhite height={24} />
189
+ ) : (
190
+ <>
191
+ {group.name
192
+ .split(" ")
193
+ .map((n) => n[0])
194
+ .join("")
195
+ .toUpperCase()}
196
+ </>
197
+ )}
198
+ </div>
199
+ <div className="historical-chat__group-name">
200
+ {group.name}
201
+ {group.title.length > 0 && <div className="title">{group.title}</div>}
202
+ </div>
203
+ <div className="historical-chat__messages">
204
+ {group.messages.map((message, i) => (
205
+ <ChatMessage
206
+ message={message}
207
+ key={`${message.id ?? ""}-${i}`}
208
+ toastContext={toastContext}
209
+ onMessageClick={(message) => {
210
+ onMessageClick?.(message);
211
+ }}
212
+ />
213
+ ))}
214
+ </div>
215
+ </>
216
+ )}
217
+ </div>
218
+ ))}
219
+ <div id="anchor" ref={chatRef}></div>
220
+ </div>
221
+ {lastMessage && (
222
+ <div className="historical-chat__toolbar">
223
+ {showComment && (
224
+ <div className="historical-chat__toolbar-row">
225
+ <Track gap={16} justify="between">
226
+ {editingComment || editingComment === "" ? (
227
+ <FormTextarea
228
+ name="comment"
229
+ label={t("global.comment")}
230
+ value={editingComment}
231
+ hideLabel
232
+ onChange={(e) => setEditingComment(e.target.value)}
233
+ />
234
+ ) : (
235
+ <p className={`historical-chat__comment-text ${chat.comment ? "" : "placeholder"}`}>
236
+ {chat.comment ?? t("chat.history.addACommentToTheConversation")}
237
+ </p>
238
+ )}
239
+ {editingComment || editingComment === "" ? (
240
+ <Button
241
+ appearance="text"
242
+ onClick={() => {
243
+ onCommentChange(editingComment);
244
+ setEditingComment(null);
245
+ }}
246
+ >
247
+ <Icon icon={<MdOutlineSave />} />
248
+ {t("global.save")}
249
+ </Button>
250
+ ) : (
251
+ <Button appearance="text" onClick={() => setEditingComment(chat.comment ?? "")}>
252
+ <Icon icon={<MdOutlineModeEditOutline />} />
253
+ {t("global.edit")}
254
+ </Button>
255
+ )}
256
+ </Track>
257
+ </div>
258
+ )}
259
+ {showStatus && statuses.length > 0 && (
260
+ <div className="historical-chat__toolbar-row">
261
+ <FormSelect
262
+ name="chatStatus"
263
+ label={t("chat.chatStatus")}
264
+ direction="up"
265
+ defaultValue={status ?? ""}
266
+ onSelectionChange={(selection) => (selection ? onChatStatusChange(selection.value) : null)}
267
+ options={statuses.map((status) => ({
268
+ label: t(`chat.events.${status}`, { date: "" }),
269
+ value: status,
270
+ }))}
271
+ />
272
+ </div>
273
+ )}
274
+ </div>
275
+ )}
276
+ </div>
277
+ <div id="anchor" ref={chatRef}></div>
278
+ </div>
279
+ );
280
+ };
281
+
282
+ export default HistoricalChat;
@@ -0,0 +1,17 @@
1
+ @import 'src/styles/tools/spacing';
2
+
3
+ .icon {
4
+ display: inline-flex;
5
+ align-items: center;
6
+ justify-content: center;
7
+
8
+ &--small {
9
+ width: get-spacing(haapsalu);
10
+ height: get-spacing(haapsalu);
11
+ }
12
+
13
+ &--medium {
14
+ width: get-spacing(kuressaare);
15
+ height: get-spacing(kuressaare);
16
+ }
17
+ }
@@ -0,0 +1,26 @@
1
+ import React, { CSSProperties, forwardRef, ReactNode, StyleHTMLAttributes } from 'react';
2
+ import * as AccessibleIcon from '@radix-ui/react-accessible-icon';
3
+ import clsx from 'clsx';
4
+
5
+ import './Icon.scss';
6
+
7
+ type IconProps = StyleHTMLAttributes<CSSProperties> & {
8
+ label?: string | null;
9
+ icon: ReactNode;
10
+ size?: 'small' | 'medium';
11
+ };
12
+
13
+ const Icon = forwardRef<HTMLSpanElement, IconProps>(({ label, icon, size = 'small', ...rest }, ref) => {
14
+ const iconClasses = clsx(
15
+ 'icon',
16
+ `icon--${size}`,
17
+ );
18
+
19
+ return (
20
+ <AccessibleIcon.Root label={label ?? ''}>
21
+ <span ref={ref} className={iconClasses} style={rest.style}>{icon}</span>
22
+ </AccessibleIcon.Root>
23
+ );
24
+ });
25
+
26
+ export default Icon;
@@ -0,0 +1,76 @@
1
+ @import 'src/styles/tools/spacing';
2
+ @import 'src/styles/tools/color';
3
+ @import 'src/styles/settings/variables/other';
4
+ @import 'src/styles/settings/variables/typography';
5
+
6
+ .label {
7
+ $self: &;
8
+ display: flex;
9
+ padding: 1.5px 16px;
10
+ font-size: $veera-font-size-80;
11
+ font-weight: $veera-font-weight-delta;
12
+ border: 2px solid;
13
+ background-color: get-color(white);
14
+ border-radius: $veera-radius-s;
15
+ position: relative;
16
+
17
+ &--info {
18
+ color: get-color(sapphire-blue-10);
19
+ border-color: get-color(sapphire-blue-10);
20
+
21
+ #{$self} {
22
+ &__icon {
23
+ border-color: get-color(sapphire-blue-10);
24
+ }
25
+ }
26
+ }
27
+
28
+ &--warning {
29
+ color: get-color(dark-tangerine-10);
30
+ border-color: get-color(dark-tangerine-10);
31
+
32
+ #{$self} {
33
+ &__icon {
34
+ border-color: get-color(dark-tangerine-10);
35
+ }
36
+ }
37
+ }
38
+
39
+ &--error {
40
+ color: get-color(jasper-10);
41
+ border-color: get-color(jasper-10);
42
+
43
+ #{$self} {
44
+ &__icon {
45
+ border-color: get-color(jasper-10);
46
+ }
47
+ }
48
+ }
49
+
50
+ &--success {
51
+ color: get-color(sea-green-10);
52
+ border-color: get-color(sea-green-10);
53
+
54
+ #{$self} {
55
+ &__icon {
56
+ border-color: get-color(sea-green-10);
57
+ }
58
+ }
59
+ }
60
+
61
+ &__icon {
62
+ display: flex;
63
+ align-items: center;
64
+ justify-content: center;
65
+ position: absolute;
66
+ font-size: 13px;
67
+ line-height: 15px;
68
+ right: -8px;
69
+ top: 4px;
70
+ width: 16px;
71
+ height: 16px;
72
+ border-radius: 50%;
73
+ border: 2px solid;
74
+ background-color: get-color(white);
75
+ }
76
+ }
@@ -0,0 +1,40 @@
1
+ import React, { forwardRef, PropsWithChildren, ReactNode } from 'react';
2
+ import clsx from 'clsx';
3
+ import { MdOutlineCheck } from 'react-icons/md';
4
+
5
+ import { Tooltip } from '../';
6
+ import './Label.scss';
7
+
8
+ type LabelProps = {
9
+ type?: 'warning' | 'error' | 'info' | 'success';
10
+ tooltip?: ReactNode;
11
+ }
12
+
13
+ const Label = forwardRef<HTMLSpanElement, PropsWithChildren<LabelProps>>((
14
+ {
15
+ type = 'info',
16
+ tooltip,
17
+ children,
18
+ }, ref,
19
+ ) => {
20
+ const labelClasses = clsx(
21
+ 'label',
22
+ `label--${type}`,
23
+ tooltip && 'label--tooltip',
24
+ );
25
+
26
+ return (
27
+ <span ref={ref} className={labelClasses}>
28
+ {children}
29
+ {tooltip && (
30
+ <Tooltip content={tooltip}>
31
+ <span className='label__icon'>
32
+ {type === 'success' ? <MdOutlineCheck /> : 'i'}
33
+ </span>
34
+ </Tooltip>
35
+ )}
36
+ </span>
37
+ );
38
+ });
39
+
40
+ export default Label;
@@ -0,0 +1,16 @@
1
+ @import 'src/styles/tools/color';
2
+
3
+ .option-container {
4
+ display: flex;
5
+ gap: 12px;
6
+ margin: 0.3rem 0 0.7rem 0;
7
+
8
+ span {
9
+ padding: 0 0.5rem;
10
+ background-color: get-color(sea-green-12);
11
+ color: get-color(white);
12
+ border-radius: 8px;
13
+ box-shadow: 2px 1px 4px rgb(159, 159, 159);
14
+ opacity: 0.7;
15
+ }
16
+ }
@@ -0,0 +1,16 @@
1
+ import { FC } from 'react';
2
+ import './OptionMessage.scss';
3
+
4
+ type OptionMessageProps = {
5
+ options: string[];
6
+ };
7
+
8
+ const OptionMessage: FC<OptionMessageProps> = ({ options }) => {
9
+ return (
10
+ <div className='option-container'>
11
+ {options.map(option => <span key={option}>{option}</span>)}
12
+ </div>
13
+ );
14
+ };
15
+
16
+ export default OptionMessage;
@@ -0,0 +1,73 @@
1
+ @import 'src/styles/tools/spacing';
2
+ @import 'src/styles/tools/color';
3
+ @import 'src/styles/settings/variables/other';
4
+ @import 'src/styles/settings/variables/typography';
5
+
6
+ .toast {
7
+ padding: 16px;
8
+ border-radius: 5px;
9
+ border: 1px solid;
10
+ display: flex;
11
+ flex-direction: column;
12
+ gap: 8px;
13
+ position: relative;
14
+ transition: opacity 0.25s ease-out;
15
+
16
+ &__title {
17
+ display: flex;
18
+ align-items: center;
19
+ gap: 8px;
20
+ padding-right: 25px;
21
+ }
22
+
23
+ &__list {
24
+ position: fixed;
25
+ bottom: 0;
26
+ right: 0;
27
+ display: flex;
28
+ flex-direction: column;
29
+ gap: 16px;
30
+ padding: 8px;
31
+ width: 408px;
32
+ max-width: 100vw;
33
+ z-index: 9999;
34
+ list-style: none;
35
+ }
36
+
37
+ &__content {
38
+ font-size: $veera-font-size-80;
39
+
40
+ a {
41
+ display: inline;
42
+ color: get-color(sapphire-blue-10);
43
+ text-decoration: underline;
44
+ }
45
+ }
46
+
47
+ &__close {
48
+ position: absolute;
49
+ top: 16px;
50
+ right: 16px;
51
+ font-size: 20px;
52
+ }
53
+
54
+ &--success {
55
+ border-color: get-color(sea-green-10);
56
+ background-color: get-color(sea-green-0);
57
+ }
58
+
59
+ &--info {
60
+ border-color: get-color(sapphire-blue-10);
61
+ background-color: get-color(sapphire-blue-1);
62
+ }
63
+
64
+ &--error {
65
+ border-color: get-color(jasper-10);
66
+ background-color: #FCEEEE;
67
+ }
68
+
69
+ &--warning {
70
+ border-color: get-color(dark-tangerine-10);
71
+ background-color: get-color(dark-tangerine-1);
72
+ }
73
+ }
@@ -0,0 +1,54 @@
1
+ import React, { FC, useState } from 'react';
2
+ import * as RadixToast from '@radix-ui/react-toast';
3
+ import {
4
+ MdOutlineClose,
5
+ MdOutlineInfo,
6
+ MdCheckCircleOutline,
7
+ MdOutlineWarningAmber,
8
+ MdErrorOutline,
9
+ } from 'react-icons/md';
10
+ import clsx from 'clsx';
11
+
12
+ import { Icon } from '../';
13
+ import type { ToastType } from '../../context/ToastContext';
14
+ import './Toast.scss';
15
+
16
+ type ToastProps = {
17
+ toast: ToastType;
18
+ close: () => void;
19
+ };
20
+
21
+ const toastIcons = {
22
+ info: <MdOutlineInfo />,
23
+ success: <MdCheckCircleOutline />,
24
+ warning: <MdOutlineWarningAmber />,
25
+ error: <MdErrorOutline />,
26
+ };
27
+
28
+ const Toast: FC<ToastProps> = ({ toast, close }) => {
29
+ const [open, setOpen] = useState(true);
30
+
31
+ const toastClasses = clsx('toast', `toast--${toast.type}`);
32
+
33
+ return (
34
+ <RadixToast.Root
35
+ className={toastClasses}
36
+ onEscapeKeyDown={close}
37
+ open={open}
38
+ onOpenChange={setOpen}
39
+ >
40
+ <RadixToast.Title className="toast__title h5">
41
+ <Icon icon={toastIcons[toast.type]} />
42
+ {toast.title}
43
+ </RadixToast.Title>
44
+ <RadixToast.Description className="toast__content">
45
+ {toast.message}
46
+ </RadixToast.Description>
47
+ <RadixToast.Close onClick={close} className="toast__close">
48
+ <Icon icon={<MdOutlineClose />} size="medium" />
49
+ </RadixToast.Close>
50
+ </RadixToast.Root>
51
+ );
52
+ };
53
+
54
+ export default Toast;
@@ -0,0 +1,17 @@
1
+ @import 'src/styles/tools/spacing';
2
+ @import 'src/styles/tools/color';
3
+ @import 'src/styles/settings/variables/typography';
4
+
5
+ .tooltip {
6
+ background-color: get-color(white);
7
+ padding: 4px;
8
+ border-radius: 4px;
9
+ filter: drop-shadow(0px 0px 20px rgba(0, 0, 0, 0.25));
10
+ font-size: $veera-font-size-80;
11
+ max-width: 50vw;
12
+ z-index: +100;
13
+
14
+ &__arrow {
15
+ fill: get-color(white);
16
+ }
17
+ }
@@ -0,0 +1,28 @@
1
+ import React, { FC, PropsWithChildren, ReactNode } from 'react';
2
+ import * as RadixTooltip from '@radix-ui/react-tooltip';
3
+
4
+ import './Tooltip.scss';
5
+
6
+ type TooltipProps = {
7
+ content: ReactNode;
8
+ }
9
+
10
+ const Tooltip: FC<PropsWithChildren<TooltipProps>> = ({ content, children }) => {
11
+ return (
12
+ <RadixTooltip.Provider delayDuration={100}>
13
+ <RadixTooltip.Root>
14
+ <RadixTooltip.Trigger asChild>
15
+ {children}
16
+ </RadixTooltip.Trigger>
17
+ <RadixTooltip.Portal>
18
+ <RadixTooltip.Content className='tooltip'>
19
+ {content}
20
+ <RadixTooltip.Arrow className='tooltip__arrow' />
21
+ </RadixTooltip.Content>
22
+ </RadixTooltip.Portal>
23
+ </RadixTooltip.Root>
24
+ </RadixTooltip.Provider>
25
+ );
26
+ };
27
+
28
+ export default Tooltip;