@memori.ai/ui 1.0.0-alpha

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 (274) hide show
  1. package/README.md +105 -0
  2. package/dist/components/Alert/Alert.d.ts +18 -0
  3. package/dist/components/Alert/index.d.ts +2 -0
  4. package/dist/components/Button/Button.d.ts +27 -0
  5. package/dist/components/Button/index.d.ts +2 -0
  6. package/dist/components/ButtonBase/ButtonBase.d.ts +28 -0
  7. package/dist/components/ButtonBase/index.d.ts +3 -0
  8. package/dist/components/Card/Card.d.ts +13 -0
  9. package/dist/components/Card/index.d.ts +2 -0
  10. package/dist/components/Checkbox/Checkbox.d.ts +11 -0
  11. package/dist/components/Checkbox/index.d.ts +2 -0
  12. package/dist/components/ConfirmDialog/ConfirmDialog.d.ts +12 -0
  13. package/dist/components/ConfirmDialog/index.d.ts +2 -0
  14. package/dist/components/Drawer/Drawer.d.ts +32 -0
  15. package/dist/components/Drawer/index.d.ts +2 -0
  16. package/dist/components/Dropdown/Dropdown.d.ts +11 -0
  17. package/dist/components/Dropdown/index.d.ts +2 -0
  18. package/dist/components/Expandable/Expandable.d.ts +15 -0
  19. package/dist/components/Expandable/helpers.d.ts +3 -0
  20. package/dist/components/Expandable/index.d.ts +2 -0
  21. package/dist/components/InputBase/InputBase.d.ts +49 -0
  22. package/dist/components/InputBase/index.d.ts +3 -0
  23. package/dist/components/Modal/Modal.d.ts +16 -0
  24. package/dist/components/Modal/index.d.ts +2 -0
  25. package/dist/components/Select/Select.d.ts +16 -0
  26. package/dist/components/Select/index.d.ts +2 -0
  27. package/dist/components/Slider/Slider.d.ts +12 -0
  28. package/dist/components/Slider/index.d.ts +2 -0
  29. package/dist/components/Spin/Spin.d.ts +9 -0
  30. package/dist/components/Spin/index.d.ts +2 -0
  31. package/dist/components/Tooltip/Tooltip.d.ts +11 -0
  32. package/dist/components/Tooltip/index.d.ts +2 -0
  33. package/dist/i18n/I18nWrapper.d.ts +6 -0
  34. package/dist/icons/AI.d.ts +5 -0
  35. package/dist/icons/Alert.d.ts +5 -0
  36. package/dist/icons/ArrowUp.d.ts +5 -0
  37. package/dist/icons/Bug.d.ts +5 -0
  38. package/dist/icons/Chat.d.ts +5 -0
  39. package/dist/icons/ChevronDown.d.ts +5 -0
  40. package/dist/icons/ChevronLeft.d.ts +5 -0
  41. package/dist/icons/ChevronRight.d.ts +5 -0
  42. package/dist/icons/ChevronUp.d.ts +5 -0
  43. package/dist/icons/Clear.d.ts +5 -0
  44. package/dist/icons/Close.d.ts +6 -0
  45. package/dist/icons/Code.d.ts +5 -0
  46. package/dist/icons/Copy.d.ts +5 -0
  47. package/dist/icons/DeepThought.d.ts +5 -0
  48. package/dist/icons/Delete.d.ts +5 -0
  49. package/dist/icons/Document.d.ts +5 -0
  50. package/dist/icons/Download.d.ts +5 -0
  51. package/dist/icons/Edit.d.ts +5 -0
  52. package/dist/icons/Expand.d.ts +5 -0
  53. package/dist/icons/Eye.d.ts +5 -0
  54. package/dist/icons/EyeInvisible.d.ts +5 -0
  55. package/dist/icons/Facebook.d.ts +5 -0
  56. package/dist/icons/Feedback.d.ts +5 -0
  57. package/dist/icons/File.d.ts +5 -0
  58. package/dist/icons/FileExcel.d.ts +5 -0
  59. package/dist/icons/FilePdf.d.ts +5 -0
  60. package/dist/icons/FileWord.d.ts +5 -0
  61. package/dist/icons/Fullscreen.d.ts +5 -0
  62. package/dist/icons/FullscreenExit.d.ts +5 -0
  63. package/dist/icons/Group.d.ts +5 -0
  64. package/dist/icons/History.d.ts +6 -0
  65. package/dist/icons/Image.d.ts +4 -0
  66. package/dist/icons/Info.d.ts +5 -0
  67. package/dist/icons/Link.d.ts +5 -0
  68. package/dist/icons/Linkedin.d.ts +5 -0
  69. package/dist/icons/Loading.d.ts +6 -0
  70. package/dist/icons/Logout.d.ts +5 -0
  71. package/dist/icons/Mail.d.ts +5 -0
  72. package/dist/icons/MapMarker.d.ts +5 -0
  73. package/dist/icons/MenuHorizontal.d.ts +6 -0
  74. package/dist/icons/MenuVertical.d.ts +6 -0
  75. package/dist/icons/Message.d.ts +5 -0
  76. package/dist/icons/Microphone.d.ts +5 -0
  77. package/dist/icons/Minus.d.ts +5 -0
  78. package/dist/icons/MinusCircle.d.ts +5 -0
  79. package/dist/icons/PaperClip.d.ts +5 -0
  80. package/dist/icons/Picture.d.ts +5 -0
  81. package/dist/icons/Plus.d.ts +5 -0
  82. package/dist/icons/Preview.d.ts +4 -0
  83. package/dist/icons/Print.d.ts +6 -0
  84. package/dist/icons/QuestionHelp.d.ts +5 -0
  85. package/dist/icons/Refresh.d.ts +5 -0
  86. package/dist/icons/SelectIcon.d.ts +5 -0
  87. package/dist/icons/Send.d.ts +5 -0
  88. package/dist/icons/Setting.d.ts +5 -0
  89. package/dist/icons/Share.d.ts +5 -0
  90. package/dist/icons/Sound.d.ts +5 -0
  91. package/dist/icons/SoundDeactivated.d.ts +5 -0
  92. package/dist/icons/Telegram.d.ts +5 -0
  93. package/dist/icons/ThumbDown.d.ts +5 -0
  94. package/dist/icons/ThumbUp.d.ts +5 -0
  95. package/dist/icons/Translation.d.ts +5 -0
  96. package/dist/icons/Twitter.d.ts +5 -0
  97. package/dist/icons/Upload.d.ts +4 -0
  98. package/dist/icons/User.d.ts +5 -0
  99. package/dist/icons/Warning.d.ts +5 -0
  100. package/dist/icons/WhatsApp.d.ts +5 -0
  101. package/dist/index.d.ts +13 -0
  102. package/dist/memori-ai-ui.css +2 -0
  103. package/dist/memori-ai-ui.es.js +6400 -0
  104. package/dist/memori-ai-ui.umd.js +23 -0
  105. package/package.json +97 -0
  106. package/src/components/Alert/Alert.css +95 -0
  107. package/src/components/Alert/Alert.stories.tsx +156 -0
  108. package/src/components/Alert/Alert.test.tsx +122 -0
  109. package/src/components/Alert/Alert.tsx +124 -0
  110. package/src/components/Alert/__snapshots__/Alert.test.tsx.snap +67 -0
  111. package/src/components/Alert/index.tsx +2 -0
  112. package/src/components/Button/Button.css +173 -0
  113. package/src/components/Button/Button.stories.tsx +173 -0
  114. package/src/components/Button/Button.test.tsx +80 -0
  115. package/src/components/Button/Button.tsx +104 -0
  116. package/src/components/Button/__snapshots__/Button.test.tsx.snap +204 -0
  117. package/src/components/Button/index.tsx +2 -0
  118. package/src/components/ButtonBase/ButtonBase.stories.tsx +194 -0
  119. package/src/components/ButtonBase/ButtonBase.tsx +84 -0
  120. package/src/components/ButtonBase/index.tsx +3 -0
  121. package/src/components/ButtonBase/styles.module.css +135 -0
  122. package/src/components/Card/Card.css +67 -0
  123. package/src/components/Card/Card.stories.tsx +104 -0
  124. package/src/components/Card/Card.test.tsx +67 -0
  125. package/src/components/Card/Card.tsx +58 -0
  126. package/src/components/Card/__snapshots__/Card.test.tsx.snap +321 -0
  127. package/src/components/Card/index.tsx +2 -0
  128. package/src/components/Checkbox/Checkbox.css +114 -0
  129. package/src/components/Checkbox/Checkbox.stories.tsx +77 -0
  130. package/src/components/Checkbox/Checkbox.test.tsx +50 -0
  131. package/src/components/Checkbox/Checkbox.tsx +61 -0
  132. package/src/components/Checkbox/__snapshots__/Checkbox.test.tsx.snap +131 -0
  133. package/src/components/Checkbox/index.tsx +2 -0
  134. package/src/components/ConfirmDialog/ConfirmDialog.css +44 -0
  135. package/src/components/ConfirmDialog/ConfirmDialog.stories.tsx +235 -0
  136. package/src/components/ConfirmDialog/ConfirmDialog.test.tsx +129 -0
  137. package/src/components/ConfirmDialog/ConfirmDialog.tsx +73 -0
  138. package/src/components/ConfirmDialog/__snapshots__/ConfirmDialog.test.tsx.snap +11 -0
  139. package/src/components/ConfirmDialog/index.tsx +2 -0
  140. package/src/components/Details/Details.css +66 -0
  141. package/src/components/Details/Details.stories.tsx +67 -0
  142. package/src/components/Drawer/Drawer.css +210 -0
  143. package/src/components/Drawer/Drawer.stories.tsx +275 -0
  144. package/src/components/Drawer/Drawer.test.tsx +158 -0
  145. package/src/components/Drawer/Drawer.tsx +242 -0
  146. package/src/components/Drawer/__snapshots__/Drawer.test.tsx.snap +21 -0
  147. package/src/components/Drawer/index.tsx +2 -0
  148. package/src/components/Dropdown/Dropdown.css +177 -0
  149. package/src/components/Dropdown/Dropdown.stories.tsx +73 -0
  150. package/src/components/Dropdown/Dropdown.test.tsx +88 -0
  151. package/src/components/Dropdown/Dropdown.tsx +88 -0
  152. package/src/components/Dropdown/__snapshots__/Dropdown.test.tsx.snap +141 -0
  153. package/src/components/Dropdown/index.tsx +2 -0
  154. package/src/components/Expandable/Expandable.css +15 -0
  155. package/src/components/Expandable/Expandable.stories.tsx +196 -0
  156. package/src/components/Expandable/Expandable.test.tsx +143 -0
  157. package/src/components/Expandable/Expandable.tsx +122 -0
  158. package/src/components/Expandable/__snapshots__/Expandable.test.tsx.snap +199 -0
  159. package/src/components/Expandable/helpers.ts +16 -0
  160. package/src/components/Expandable/index.tsx +2 -0
  161. package/src/components/InputBase/InputBase.stories.tsx +237 -0
  162. package/src/components/InputBase/InputBase.tsx +105 -0
  163. package/src/components/InputBase/index.tsx +3 -0
  164. package/src/components/InputBase/styles.module.css +96 -0
  165. package/src/components/Modal/Modal.css +95 -0
  166. package/src/components/Modal/Modal.stories.tsx +184 -0
  167. package/src/components/Modal/Modal.test.tsx +129 -0
  168. package/src/components/Modal/Modal.tsx +124 -0
  169. package/src/components/Modal/__snapshots__/Modal.test.tsx.snap +17 -0
  170. package/src/components/Modal/index.tsx +2 -0
  171. package/src/components/Select/Select.css +134 -0
  172. package/src/components/Select/Select.stories.tsx +85 -0
  173. package/src/components/Select/Select.test.tsx +85 -0
  174. package/src/components/Select/Select.tsx +84 -0
  175. package/src/components/Select/__snapshots__/Select.test.tsx.snap +261 -0
  176. package/src/components/Select/index.tsx +2 -0
  177. package/src/components/Slider/Slider.css +194 -0
  178. package/src/components/Slider/Slider.stories.tsx +85 -0
  179. package/src/components/Slider/Slider.test.tsx +55 -0
  180. package/src/components/Slider/Slider.tsx +157 -0
  181. package/src/components/Slider/__snapshots__/Slider.test.tsx.snap +419 -0
  182. package/src/components/Slider/index.tsx +2 -0
  183. package/src/components/Spin/Spin.css +31 -0
  184. package/src/components/Spin/Spin.stories.tsx +65 -0
  185. package/src/components/Spin/Spin.test.tsx +47 -0
  186. package/src/components/Spin/Spin.tsx +34 -0
  187. package/src/components/Spin/__snapshots__/Spin.test.tsx.snap +193 -0
  188. package/src/components/Spin/index.tsx +2 -0
  189. package/src/components/Table/Table.css +130 -0
  190. package/src/components/Table/Table.stories.tsx +274 -0
  191. package/src/components/Tooltip/Tooltip.css +160 -0
  192. package/src/components/Tooltip/Tooltip.stories.tsx +136 -0
  193. package/src/components/Tooltip/Tooltip.test.tsx +110 -0
  194. package/src/components/Tooltip/Tooltip.tsx +46 -0
  195. package/src/components/Tooltip/__snapshots__/Tooltip.test.tsx.snap +163 -0
  196. package/src/components/Tooltip/index.tsx +2 -0
  197. package/src/components/definitions.stories.tsx +150 -0
  198. package/src/i18n/I18nWrapper.tsx +13 -0
  199. package/src/i18n/i18n.ts +34 -0
  200. package/src/icons/AI.tsx +38 -0
  201. package/src/icons/Alert.tsx +31 -0
  202. package/src/icons/ArrowUp.tsx +28 -0
  203. package/src/icons/Bug.tsx +81 -0
  204. package/src/icons/Chat.tsx +30 -0
  205. package/src/icons/ChevronDown.tsx +26 -0
  206. package/src/icons/ChevronLeft.tsx +28 -0
  207. package/src/icons/ChevronRight.tsx +28 -0
  208. package/src/icons/ChevronUp.tsx +24 -0
  209. package/src/icons/Clear.tsx +23 -0
  210. package/src/icons/Close.tsx +30 -0
  211. package/src/icons/Code.tsx +30 -0
  212. package/src/icons/Copy.tsx +30 -0
  213. package/src/icons/DeepThought.tsx +38 -0
  214. package/src/icons/Delete.tsx +23 -0
  215. package/src/icons/Document.tsx +52 -0
  216. package/src/icons/Download.tsx +23 -0
  217. package/src/icons/Edit.tsx +17 -0
  218. package/src/icons/Expand.tsx +23 -0
  219. package/src/icons/Eye.tsx +17 -0
  220. package/src/icons/EyeInvisible.tsx +24 -0
  221. package/src/icons/Facebook.tsx +23 -0
  222. package/src/icons/Feedback.tsx +27 -0
  223. package/src/icons/File.tsx +17 -0
  224. package/src/icons/FileExcel.tsx +23 -0
  225. package/src/icons/FilePdf.tsx +23 -0
  226. package/src/icons/FileWord.tsx +23 -0
  227. package/src/icons/Fullscreen.tsx +23 -0
  228. package/src/icons/FullscreenExit.tsx +23 -0
  229. package/src/icons/Group.tsx +30 -0
  230. package/src/icons/History.tsx +33 -0
  231. package/src/icons/Image.tsx +37 -0
  232. package/src/icons/Info.tsx +37 -0
  233. package/src/icons/Link.tsx +17 -0
  234. package/src/icons/Linkedin.tsx +23 -0
  235. package/src/icons/Loading.tsx +28 -0
  236. package/src/icons/Logout.tsx +33 -0
  237. package/src/icons/Mail.tsx +17 -0
  238. package/src/icons/MapMarker.tsx +26 -0
  239. package/src/icons/MenuHorizontal.tsx +29 -0
  240. package/src/icons/MenuVertical.tsx +29 -0
  241. package/src/icons/Message.tsx +23 -0
  242. package/src/icons/Microphone.tsx +23 -0
  243. package/src/icons/Minus.tsx +23 -0
  244. package/src/icons/MinusCircle.tsx +24 -0
  245. package/src/icons/PaperClip.tsx +23 -0
  246. package/src/icons/Picture.tsx +23 -0
  247. package/src/icons/Plus.tsx +18 -0
  248. package/src/icons/Preview.tsx +31 -0
  249. package/src/icons/Print.tsx +37 -0
  250. package/src/icons/QuestionHelp.tsx +34 -0
  251. package/src/icons/Refresh.tsx +30 -0
  252. package/src/icons/SelectIcon.tsx +28 -0
  253. package/src/icons/Send.tsx +17 -0
  254. package/src/icons/Setting.tsx +23 -0
  255. package/src/icons/Share.tsx +23 -0
  256. package/src/icons/Sound.tsx +23 -0
  257. package/src/icons/SoundDeactivated.tsx +27 -0
  258. package/src/icons/Telegram.tsx +26 -0
  259. package/src/icons/ThumbDown.tsx +29 -0
  260. package/src/icons/ThumbUp.tsx +29 -0
  261. package/src/icons/Translation.tsx +24 -0
  262. package/src/icons/Twitter.tsx +23 -0
  263. package/src/icons/Upload.tsx +34 -0
  264. package/src/icons/User.tsx +17 -0
  265. package/src/icons/Warning.tsx +27 -0
  266. package/src/icons/WhatsApp.tsx +24 -0
  267. package/src/icons/icons.stories.css +5 -0
  268. package/src/icons/icons.stories.tsx +110 -0
  269. package/src/icons/loading.css +14 -0
  270. package/src/index.ts +15 -0
  271. package/src/styles/utils.css +17 -0
  272. package/src/styles.css +4 -0
  273. package/src/theme/variables.css +110 -0
  274. package/src/vite-env.d.ts +6 -0
@@ -0,0 +1,242 @@
1
+ import type { FC, JSX } from 'react'
2
+ import React, { useEffect, useState, useCallback } from 'react'
3
+ import { Dialog, Transition } from '@headlessui/react'
4
+ import Spin from '../Spin/Spin'
5
+ import Button from '../Button/Button'
6
+ import Close from '../../icons/Close'
7
+ import ConfirmDialog from '../ConfirmDialog/ConfirmDialog'
8
+ import cx from 'classnames'
9
+ import { useTranslation } from 'react-i18next'
10
+
11
+ import './Drawer.css'
12
+
13
+ export interface Props {
14
+ title?: string | React.ReactNode
15
+ open?: boolean
16
+ data?: any
17
+ onClose?: () => void
18
+ width?: number | string
19
+ children?: React.ReactNode
20
+ footer?: {
21
+ leftAction?: React.ReactNode
22
+ leftActionClassName?: string
23
+ onSubmit?: () => void
24
+ loading?: boolean
25
+ }
26
+ extra?: React.ReactNode
27
+ className?: string
28
+ placement?: 'left' | 'right'
29
+ description?: string | JSX.Element | React.ReactNode
30
+ loading?: boolean
31
+ animated?: boolean
32
+ closable?: boolean
33
+ widthMd?: string
34
+ widthLg?: string
35
+ confirmDialogTitle?: string
36
+ confirmDialogMessage?: string
37
+ showBackdrop?: boolean
38
+ preventBackdropClose?: boolean
39
+ enterDuration?: string
40
+ leaveDuration?: string
41
+ }
42
+
43
+ const Drawer: FC<Props> = ({
44
+ title,
45
+ open = false,
46
+ data,
47
+ onClose = () => {},
48
+ children,
49
+ width = '80%',
50
+ footer,
51
+ showBackdrop = true,
52
+ extra,
53
+ className,
54
+ placement = 'right',
55
+ description,
56
+ loading = false,
57
+ animated = true,
58
+ closable = true,
59
+ widthMd = '80%',
60
+ widthLg = '60%',
61
+ confirmDialogTitle,
62
+ confirmDialogMessage,
63
+ preventBackdropClose = false,
64
+ enterDuration = 'duration-300',
65
+ leaveDuration = 'duration-200',
66
+ }: Props) => {
67
+ const [originalData, setOriginalData] = useState<any>(null)
68
+ const [confirmDialogOpen, setConfirmDialogOpen] = useState(false)
69
+ const { t } = useTranslation()
70
+
71
+ // Set original data when drawer opens and data is available
72
+ useEffect(() => {
73
+ if (open && data && !originalData) {
74
+ setOriginalData(data)
75
+ }
76
+
77
+ // Reset original data when drawer closes
78
+ if (!open) {
79
+ setOriginalData(null)
80
+ }
81
+ }, [open, data, originalData])
82
+
83
+ // Check if data has changed
84
+ const checkChanges = useCallback(() => {
85
+ if (!data || Object.keys(data).length === 0) {
86
+ return onClose()
87
+ }
88
+
89
+ // Compare current data with original data
90
+ if (originalData && JSON.stringify(originalData) !== JSON.stringify(data)) {
91
+ setConfirmDialogOpen(true)
92
+ } else {
93
+ onClose()
94
+ }
95
+ }, [data, originalData, onClose])
96
+
97
+ // Handle drawer close
98
+ const handleClose = useCallback(() => {
99
+ checkChanges()
100
+ }, [checkChanges])
101
+
102
+ // Confirm unsaved changes
103
+ const handleConfirmUnsavedChanges = useCallback(() => {
104
+ setConfirmDialogOpen(false)
105
+ onClose()
106
+ }, [onClose])
107
+
108
+ return (
109
+ <>
110
+ <ConfirmDialog
111
+ isOpen={confirmDialogOpen}
112
+ onClose={() => setConfirmDialogOpen(false)}
113
+ onConfirm={handleConfirmUnsavedChanges}
114
+ title={confirmDialogTitle || t('confirmDialog.title')}
115
+ message={confirmDialogMessage || t('confirmDialog.message')}
116
+ confirmText={t('confirm') || 'Confirm'}
117
+ cancelText={t('cancel') || 'Cancel'}
118
+ />
119
+
120
+ <Transition
121
+ appear
122
+ show={open}
123
+ as={React.Fragment}
124
+ >
125
+ <Dialog
126
+ open={open}
127
+ onClose={preventBackdropClose ? () => {} : handleClose}
128
+ className={cx('memori-drawer', className)}
129
+ >
130
+ {showBackdrop && (
131
+ <Transition.Child
132
+ as={React.Fragment}
133
+ enter="ease-out duration-300"
134
+ enterFrom="opacity-0"
135
+ enterTo="opacity-100"
136
+ leave="ease-in duration-200"
137
+ leaveFrom="opacity-100"
138
+ leaveTo="opacity-0"
139
+ >
140
+ <div className="memori-drawer--backdrop" />
141
+ </Transition.Child>
142
+ )}
143
+ <div className="memori-drawer--container">
144
+ <div className="memori-drawer--container-scrollable">
145
+ <Transition.Child
146
+ static
147
+ as={React.Fragment}
148
+ enter={`ease-out ${enterDuration}`}
149
+ enterFrom={animated ? 'max-w-0 opacity-0' : 'opacity-0'}
150
+ enterTo="max-w-80 opacity-100"
151
+ leave={`ease-in ${leaveDuration}`}
152
+ leaveFrom="max-w-80 opacity-100"
153
+ leaveTo={animated ? 'max-w-0 opacity-0' : 'opacity-0'}
154
+ >
155
+ <Dialog.Panel
156
+ className={cx('memori-drawer--panel', {
157
+ 'memori-drawer--panel-left': placement === 'left',
158
+ 'memori-drawer--with-footer': !!footer,
159
+ })}
160
+ style={
161
+ {
162
+ '--memori-drawer--width': width,
163
+ '--memori-drawer--width--lg': widthLg,
164
+ '--memori-drawer--width--md': widthMd,
165
+ } as React.CSSProperties
166
+ }
167
+ >
168
+ {closable && (
169
+ <div className="memori-drawer--close">
170
+ <Button
171
+ shape="circle"
172
+ outlined
173
+ icon={<Close />}
174
+ onClick={handleClose}
175
+ />
176
+ </div>
177
+ )}
178
+ <Spin spinning={loading}>
179
+ <div className="memori-drawer--content">
180
+ {title && (
181
+ <Dialog.Title className="memori-drawer--title">
182
+ {title}
183
+ </Dialog.Title>
184
+ )}
185
+ {description && (
186
+ <Dialog.Description className="memori-drawer--description">
187
+ {description}
188
+ </Dialog.Description>
189
+ )}
190
+ <div className="memori-drawer--content--scrollable">
191
+ {children}
192
+ </div>
193
+ </div>
194
+ </Spin>
195
+ {footer && (
196
+ <div className="memori-drawer--footer">
197
+ {footer.leftAction && (
198
+ <div
199
+ className={
200
+ 'memori-drawer--footer-left-action ' +
201
+ (footer.leftActionClassName || '')
202
+ }
203
+ >
204
+ {footer.leftAction}
205
+ </div>
206
+ )}
207
+ {footer.onSubmit && (
208
+ <div className="memori-drawer--footer-actions">
209
+ <Button
210
+ outlined
211
+ onClick={handleClose}
212
+ >
213
+ {t('cancel')}
214
+ </Button>
215
+ <Button
216
+ htmlType="submit"
217
+ onClick={footer.onSubmit}
218
+ loading={footer.loading}
219
+ className="memori-drawer--footer-confirm"
220
+ >
221
+ {t('confirm')}
222
+ </Button>
223
+ </div>
224
+ )}
225
+ {extra && (
226
+ <div className="memori-drawer--extra">{extra}</div>
227
+ )}
228
+ </div>
229
+ )}
230
+ </Dialog.Panel>
231
+ </Transition.Child>
232
+ </div>
233
+ </div>
234
+ </Dialog>
235
+ </Transition>
236
+ </>
237
+ )
238
+ }
239
+
240
+ Drawer.displayName = 'Drawer'
241
+
242
+ export default Drawer
@@ -0,0 +1,21 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`renders Drawer loading unchanged 1`] = `<div />`;
4
+
5
+ exports[`renders Drawer non closable unchanged 1`] = `<div />`;
6
+
7
+ exports[`renders Drawer open unchanged 1`] = `<div />`;
8
+
9
+ exports[`renders Drawer side left unchanged 1`] = `<div />`;
10
+
11
+ exports[`renders Drawer unchanged 1`] = `<div />`;
12
+
13
+ exports[`renders Drawer with custom widths unchanged 1`] = `<div />`;
14
+
15
+ exports[`renders Drawer with description unchanged 1`] = `<div />`;
16
+
17
+ exports[`renders Drawer with footer unchanged 1`] = `<div />`;
18
+
19
+ exports[`renders Drawer with footer unchanged 2`] = `<div />`;
20
+
21
+ exports[`renders Drawer with title unchanged 1`] = `<div />`;
@@ -0,0 +1,2 @@
1
+ import Drawer from './Drawer'
2
+ export { Drawer }
@@ -0,0 +1,177 @@
1
+ .memori-dropdown {
2
+ position: relative;
3
+ display: inline-block;
4
+ }
5
+
6
+ .memori-dropdown--trigger {
7
+ cursor: pointer;
8
+ }
9
+
10
+ .memori-dropdown--content {
11
+ position: absolute;
12
+ z-index: 10001;
13
+ overflow: hidden;
14
+ min-width: 280px;
15
+ max-width: 320px;
16
+ border: 1px solid rgb(0 0 0 / 8%);
17
+ border-radius: 12px;
18
+ animation: dropdown-fade-in 0.2s ease-out;
19
+ background: white;
20
+ box-shadow:
21
+ 0 10px 25px rgb(0 0 0 / 15%),
22
+ 0 4px 10px rgb(0 0 0 / 10%);
23
+ }
24
+
25
+ @keyframes dropdown-fade-in {
26
+ from {
27
+ opacity: 0;
28
+ transform: translateY(-8px) scale(0.95);
29
+ }
30
+
31
+ to {
32
+ opacity: 1;
33
+ transform: translateY(0) scale(1);
34
+ }
35
+ }
36
+
37
+ /* Placement variants */
38
+ .memori-dropdown--content--bottom-right {
39
+ top: 100%;
40
+ right: 0;
41
+ margin-top: 8px;
42
+ }
43
+
44
+ .memori-dropdown--content--bottom-left {
45
+ top: 100%;
46
+ left: 0;
47
+ margin-top: 8px;
48
+ }
49
+
50
+ .memori-dropdown--content--top-right {
51
+ right: 0;
52
+ bottom: 100%;
53
+ margin-bottom: 8px;
54
+ }
55
+
56
+ .memori-dropdown--content--top-left {
57
+ bottom: 100%;
58
+ left: 0;
59
+ margin-bottom: 8px;
60
+ }
61
+
62
+ /* User profile dropdown specific styles */
63
+ .memori-dropdown--user-profile {
64
+ padding: 16px;
65
+
66
+ /* border-bottom: 1px solid #f1f5f9; */
67
+ }
68
+
69
+ .memori-dropdown--user-info {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 12px;
73
+ }
74
+
75
+ .memori-dropdown--avatar {
76
+ width: 48px;
77
+ height: 48px;
78
+ border: 2px solid #e2e8f0;
79
+ border-radius: 50%;
80
+ object-fit: cover;
81
+ }
82
+
83
+ .memori-dropdown--avatar-placeholder {
84
+ display: flex;
85
+ width: 40px;
86
+ height: 40px;
87
+ align-items: center;
88
+ justify-content: center;
89
+ border: 2px solid #e2e8f0;
90
+ border-radius: 50%;
91
+ background: var(--memori-primary);
92
+ color: white;
93
+ font-size: 16px;
94
+ font-weight: 600;
95
+ }
96
+
97
+ .memori-dropdown--user-details {
98
+ min-width: 0;
99
+ flex: 1;
100
+ }
101
+
102
+ .memori-dropdown--user-name {
103
+ overflow: hidden;
104
+ margin: 0;
105
+ color: #1e293b;
106
+ font-size: 14px;
107
+ font-weight: 600;
108
+ text-align: start;
109
+ text-overflow: ellipsis;
110
+ white-space: nowrap;
111
+ }
112
+
113
+ .memori-dropdown--user-email {
114
+ overflow: hidden;
115
+ margin: 2px 0 0;
116
+ color: #64748b;
117
+ font-size: 12px;
118
+ text-align: start;
119
+ text-overflow: ellipsis;
120
+ white-space: nowrap;
121
+ }
122
+
123
+ .memori-dropdown--user-badge {
124
+ border-radius: 12px;
125
+ margin-top: 6px;
126
+ color: rgb(119 119 119);
127
+ font-size: 11px;
128
+ font-weight: 500;
129
+ text-align: start;
130
+ }
131
+
132
+ .memori-dropdown--actions {
133
+ padding: 8px 0;
134
+ }
135
+
136
+ .memori-dropdown--action-button {
137
+ display: flex;
138
+ width: 100%;
139
+ align-items: center;
140
+ padding: 12px 16px;
141
+ border: none;
142
+ background: none;
143
+ color: #374151;
144
+ cursor: pointer;
145
+ font-size: 14px;
146
+ gap: 8px;
147
+ text-align: left;
148
+ transition: background-color 0.2s ease;
149
+ }
150
+
151
+ .memori-dropdown--action-button:hover {
152
+ background: #f8fafc;
153
+ }
154
+
155
+ .memori-dropdown--action-button--logout {
156
+ border-top: 1px solid #f1f5f9;
157
+ margin-top: 4px;
158
+ color: #dc2626;
159
+ }
160
+
161
+ .memori-dropdown--action-button--logout:hover {
162
+ background: #fef2f2;
163
+ }
164
+
165
+ .memori-dropdown--action-icon {
166
+ width: 16px;
167
+ height: 16px;
168
+ flex-shrink: 0;
169
+ }
170
+
171
+ /* Responsive adjustments */
172
+ @media (width <= 640px) {
173
+ .memori-dropdown--content {
174
+ min-width: 260px;
175
+ max-width: 280px;
176
+ }
177
+ }
@@ -0,0 +1,73 @@
1
+ import React from 'react'
2
+ import type { Meta, StoryObj } from '@storybook/react'
3
+ import Dropdown from './Dropdown'
4
+ import Button from '../Button/Button'
5
+
6
+ const meta = {
7
+ title: 'UI/Dropdown',
8
+ component: Dropdown,
9
+ argTypes: {
10
+ open: {
11
+ control: {
12
+ type: 'boolean',
13
+ },
14
+ },
15
+ },
16
+ tags: ['autodocs'],
17
+ parameters: {
18
+ controls: { expanded: true },
19
+ },
20
+ } satisfies Meta<typeof Dropdown>
21
+
22
+ export default meta
23
+
24
+ type Story = StoryObj<typeof meta>
25
+
26
+ export const Default: Story = {
27
+ args: {
28
+ open: false,
29
+ trigger: <Button>Open</Button>,
30
+ },
31
+ }
32
+
33
+ export const Open: Story = {
34
+ args: {
35
+ open: true,
36
+ trigger: <Button>Open</Button>,
37
+ },
38
+ }
39
+
40
+ export const WithClassName: Story = {
41
+ args: {
42
+ className: 'custom-dropdown',
43
+ trigger: <Button>Open</Button>,
44
+ },
45
+ }
46
+
47
+ export const WithPlacementBottomLeft: Story = {
48
+ args: {
49
+ placement: 'bottom-left',
50
+ trigger: <Button>Open</Button>,
51
+ },
52
+ }
53
+
54
+ export const WithPlacementBottomRight: Story = {
55
+ args: {
56
+ placement: 'bottom-right',
57
+ trigger: <Button>Open</Button>,
58
+ },
59
+ }
60
+
61
+ export const WithPlacementTopLeft: Story = {
62
+ args: {
63
+ placement: 'top-left',
64
+ trigger: <Button>Open</Button>,
65
+ },
66
+ }
67
+
68
+ export const WithPlacementTopRight: Story = {
69
+ args: {
70
+ placement: 'top-right',
71
+ trigger: <Button>Open</Button>,
72
+ },
73
+ }
@@ -0,0 +1,88 @@
1
+ import React from 'react'
2
+ import { expect, it } from 'vitest'
3
+ import { render } from '@testing-library/react'
4
+ import Dropdown from './Dropdown'
5
+ import Button from '../Button/Button'
6
+
7
+ it('renders Dropdown unchanged', () => {
8
+ const { container } = render(
9
+ <Dropdown trigger={<Button>Open</Button>}>
10
+ <div>Content</div>
11
+ </Dropdown>,
12
+ )
13
+ expect(container).toMatchSnapshot()
14
+ })
15
+
16
+ it('renders Dropdown open unchanged', () => {
17
+ const { container } = render(
18
+ <Dropdown
19
+ open={true}
20
+ trigger={<Button>Open</Button>}
21
+ >
22
+ <div>Content</div>
23
+ </Dropdown>,
24
+ )
25
+ expect(container).toMatchSnapshot()
26
+ })
27
+
28
+ it('renders Dropdown with className unchanged', () => {
29
+ const { container } = render(
30
+ <Dropdown
31
+ className="custom-dropdown"
32
+ trigger={<Button>Open</Button>}
33
+ >
34
+ <div>Content</div>
35
+ </Dropdown>,
36
+ )
37
+ const dropdown = container.querySelector('.memori-dropdown')
38
+ expect(dropdown?.classList.contains('custom-dropdown')).toBe(true)
39
+ expect(container).toMatchSnapshot()
40
+ })
41
+
42
+ it('renders Dropdown with placement bottom-left unchanged', () => {
43
+ const { container } = render(
44
+ <Dropdown
45
+ placement="bottom-left"
46
+ trigger={<Button>Open</Button>}
47
+ >
48
+ <div>Content</div>
49
+ </Dropdown>,
50
+ )
51
+ expect(container).toMatchSnapshot()
52
+ })
53
+
54
+ it('renders Dropdown with placement bottom-right unchanged', () => {
55
+ const { container } = render(
56
+ <Dropdown
57
+ placement="bottom-right"
58
+ trigger={<Button>Open</Button>}
59
+ >
60
+ <div>Content</div>
61
+ </Dropdown>,
62
+ )
63
+ expect(container).toMatchSnapshot()
64
+ })
65
+
66
+ it('renders Dropdown with placement top-left unchanged', () => {
67
+ const { container } = render(
68
+ <Dropdown
69
+ placement="top-left"
70
+ trigger={<Button>Open</Button>}
71
+ >
72
+ <div>Content</div>
73
+ </Dropdown>,
74
+ )
75
+ expect(container).toMatchSnapshot()
76
+ })
77
+
78
+ it('renders Dropdown with placement top-right unchanged', () => {
79
+ const { container } = render(
80
+ <Dropdown
81
+ placement="top-right"
82
+ trigger={<Button>Open</Button>}
83
+ >
84
+ <div>Content</div>
85
+ </Dropdown>,
86
+ )
87
+ expect(container).toMatchSnapshot()
88
+ })
@@ -0,0 +1,88 @@
1
+ import React, { useRef, useEffect, useState } from 'react'
2
+ import cx from 'classnames'
3
+
4
+ import './Dropdown.css'
5
+
6
+ export interface Props {
7
+ open?: boolean
8
+ onClose?: () => void
9
+ children?: React.ReactNode
10
+ className?: string
11
+ trigger?: React.ReactNode
12
+ placement?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right'
13
+ }
14
+
15
+ const Dropdown: React.FC<Props> = ({
16
+ open = false,
17
+ onClose,
18
+ children,
19
+ className,
20
+ trigger,
21
+ placement = 'bottom-right',
22
+ }) => {
23
+ const dropdownRef = useRef<HTMLDivElement>(null)
24
+ const [isOpen, setIsOpen] = useState(open)
25
+
26
+ useEffect(() => {
27
+ setIsOpen(open)
28
+ }, [open])
29
+
30
+ useEffect(() => {
31
+ const handleClickOutside = (event: MouseEvent) => {
32
+ if (
33
+ dropdownRef.current &&
34
+ !dropdownRef.current.contains(event.target as Node)
35
+ ) {
36
+ setIsOpen(false)
37
+ onClose?.()
38
+ }
39
+ }
40
+
41
+ if (isOpen) {
42
+ document.addEventListener('mousedown', handleClickOutside)
43
+ }
44
+
45
+ return () => {
46
+ document.removeEventListener('mousedown', handleClickOutside)
47
+ }
48
+ }, [isOpen, onClose])
49
+
50
+ const handleTriggerClick = () => {
51
+ setIsOpen(!isOpen)
52
+ }
53
+
54
+ const handleTriggerKeyDown = (event: React.KeyboardEvent) => {
55
+ if (event.key === 'Enter' || event.key === ' ') {
56
+ event.preventDefault()
57
+ setIsOpen(!isOpen)
58
+ }
59
+ }
60
+
61
+ return (
62
+ <div
63
+ className={cx('memori-dropdown', className)}
64
+ ref={dropdownRef}
65
+ >
66
+ <div
67
+ className="memori-dropdown--trigger"
68
+ onClick={handleTriggerClick}
69
+ onKeyDown={handleTriggerKeyDown}
70
+ tabIndex={0}
71
+ >
72
+ {trigger}
73
+ </div>
74
+ {isOpen && (
75
+ <div
76
+ className={cx(
77
+ 'memori-dropdown--content',
78
+ `memori-dropdown--content--${placement}`,
79
+ )}
80
+ >
81
+ {children}
82
+ </div>
83
+ )}
84
+ </div>
85
+ )
86
+ }
87
+
88
+ export default Dropdown