@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,432 @@
1
+ import React, { CSSProperties, FC, ReactNode, useId } from 'react';
2
+ import {
3
+ ColumnDef,
4
+ useReactTable,
5
+ getCoreRowModel,
6
+ flexRender,
7
+ getSortedRowModel,
8
+ SortingState,
9
+ FilterFn,
10
+ getFilteredRowModel,
11
+ VisibilityState,
12
+ getPaginationRowModel,
13
+ PaginationState,
14
+ TableMeta,
15
+ Row,
16
+ RowData,
17
+ ColumnFiltersState,
18
+ ColumnPinningState,
19
+ } from '@tanstack/react-table';
20
+ import { RankingInfo, rankItem } from '@tanstack/match-sorter-utils';
21
+ import {
22
+ MdUnfoldMore,
23
+ MdExpandMore,
24
+ MdExpandLess,
25
+ MdOutlineEast,
26
+ MdOutlineWest,
27
+ } from 'react-icons/md';
28
+ import clsx from 'clsx';
29
+ import { Link, useSearchParams } from 'react-router-dom';
30
+ import { useTranslation } from 'react-i18next';
31
+
32
+ import { Icon, Track } from '../';
33
+ import Filter from './Filter';
34
+ import './DataTable.scss';
35
+
36
+ type DataTableProps = {
37
+ data: any;
38
+ columns: ColumnDef<any, any>[];
39
+ tableBodyPrefix?: ReactNode;
40
+ isClientSide?: boolean;
41
+ sortable?: boolean;
42
+ filterable?: boolean;
43
+ pagination?: PaginationState;
44
+ sorting?: SortingState;
45
+ columnFilters?: ColumnFiltersState;
46
+ columnPinning?: ColumnPinningState;
47
+ setPagination?: (state: PaginationState) => void;
48
+ setSorting?: (state: SortingState) => void;
49
+ setFiltering?: (state: ColumnFiltersState) => void;
50
+ globalFilter?: string;
51
+ setGlobalFilter?: React.Dispatch<React.SetStateAction<string>>;
52
+ columnVisibility?: VisibilityState;
53
+ setColumnVisibility?: React.Dispatch<React.SetStateAction<VisibilityState>>;
54
+ disableHead?: boolean;
55
+ pagesCount?: number;
56
+ meta?: TableMeta<any>;
57
+ selectedRow?: (row: Row<any>) => boolean;
58
+ };
59
+
60
+ type ColumnMeta = {
61
+ meta: {
62
+ size: number | string;
63
+ sticky: 'left' | 'right';
64
+ };
65
+ };
66
+
67
+ type CustomColumnDef = ColumnDef<any> & ColumnMeta;
68
+
69
+ declare module '@tanstack/table-core' {
70
+ interface FilterFns {
71
+ fuzzy: FilterFn<unknown>;
72
+ }
73
+
74
+ interface FilterMeta {
75
+ itemRank: RankingInfo;
76
+ }
77
+ }
78
+
79
+ declare module '@tanstack/react-table' {
80
+ interface TableMeta<TData extends RowData> {
81
+ getRowStyles: (row: Row<TData>) => CSSProperties;
82
+ }
83
+ class Column<TData extends RowData> {
84
+ columnDef: CustomColumnDef;
85
+ }
86
+ }
87
+
88
+ const fuzzyFilter: FilterFn<any> = (row, columnId, value, addMeta) => {
89
+ const itemRank = rankItem(row.getValue(columnId), value);
90
+ addMeta({
91
+ itemRank,
92
+ });
93
+ return itemRank.passed;
94
+ };
95
+
96
+ const DataTable: FC<DataTableProps> = ({
97
+ data,
98
+ columns,
99
+ isClientSide = true,
100
+ tableBodyPrefix,
101
+ sortable,
102
+ filterable,
103
+ pagination,
104
+ sorting,
105
+ columnFilters,
106
+ columnPinning,
107
+ setPagination,
108
+ setSorting,
109
+ setFiltering,
110
+ globalFilter,
111
+ setGlobalFilter,
112
+ columnVisibility,
113
+ setColumnVisibility,
114
+ disableHead,
115
+ pagesCount,
116
+ meta,
117
+ selectedRow,
118
+ }) => {
119
+ const id = useId();
120
+ const { t } = useTranslation();
121
+ const [searchParams, setSearchParams] = useSearchParams();
122
+ const table = useReactTable({
123
+ data,
124
+ columns,
125
+ filterFns: {
126
+ fuzzy: fuzzyFilter,
127
+ },
128
+ state: {
129
+ sorting,
130
+ globalFilter,
131
+ columnPinning: columnPinning ?? {
132
+ left: [],
133
+ right: [],
134
+ },
135
+ columnVisibility,
136
+ ...{ pagination },
137
+ ...{ columnFilters },
138
+ },
139
+ enableColumnPinning: columnPinning != undefined ? true : false,
140
+ meta,
141
+ onColumnFiltersChange: (updater) => {
142
+ if (typeof updater !== 'function') return;
143
+ setFiltering?.(updater(table.getState().columnFilters));
144
+ },
145
+ onGlobalFilterChange: setGlobalFilter,
146
+ onColumnVisibilityChange: setColumnVisibility,
147
+ globalFilterFn: fuzzyFilter,
148
+ onSortingChange: (updater) => {
149
+ if (typeof updater !== 'function') return;
150
+ setSorting?.(updater(table.getState().sorting));
151
+ },
152
+ onPaginationChange: (updater) => {
153
+ if (typeof updater !== 'function') return;
154
+ setPagination?.(updater(table.getState().pagination));
155
+ },
156
+ getCoreRowModel: getCoreRowModel(),
157
+ getFilteredRowModel: getFilteredRowModel(),
158
+ ...(pagination && { getPaginationRowModel: getPaginationRowModel() }),
159
+ ...(sortable && { getSortedRowModel: getSortedRowModel() }),
160
+ manualPagination: isClientSide ? undefined : true,
161
+ manualFiltering: isClientSide ? undefined : true,
162
+ manualSorting: isClientSide ? undefined : true,
163
+ pageCount: isClientSide ? undefined : pagesCount,
164
+ });
165
+
166
+ const getShownPageIndexes = () => {
167
+ if (!table.getState().pagination) return [];
168
+
169
+ const pageOffset = 2;
170
+ const currentPage = table.getState().pagination.pageIndex;
171
+ const startPage = Math.max(0, currentPage - pageOffset);
172
+ const endPage = Math.min(
173
+ table.getPageCount() - 1,
174
+ currentPage + pageOffset
175
+ );
176
+ const pages = [];
177
+ for (let i = startPage; i <= endPage; i++) {
178
+ pages.push(i);
179
+ }
180
+ return pages;
181
+ };
182
+ const pageIndexes = getShownPageIndexes();
183
+
184
+ const searchParamsWithoutPage = new URLSearchParams(searchParams);
185
+ searchParamsWithoutPage.delete('page');
186
+
187
+ return (
188
+ <>
189
+ <div className="data-table__scrollWrapper">
190
+ <table className="data-table">
191
+ {!disableHead && (
192
+ <thead>
193
+ {table.getHeaderGroups().map((headerGroup) => (
194
+ <tr key={headerGroup.id}>
195
+ {headerGroup.headers.map((header) => (
196
+ <th
197
+ key={header.id}
198
+ style={{
199
+ width: header.column.columnDef.meta?.size,
200
+ position: header.column.columnDef.meta?.sticky
201
+ ? 'sticky'
202
+ : undefined,
203
+ left:
204
+ header.column.columnDef.meta?.sticky === 'left'
205
+ ? `${header.column.getAfter('left') * 0.675}px`
206
+ : undefined,
207
+ right:
208
+ header.column.columnDef.meta?.sticky === 'right'
209
+ ? `${header.column.getAfter('right') * 0.675}px`
210
+ : undefined,
211
+ backgroundColor: 'white',
212
+ zIndex: header.column.columnDef.meta?.sticky ? 1 : 0,
213
+ }}
214
+ >
215
+ {header.isPlaceholder ? null : (
216
+ <Track gap={8}>
217
+ {sortable && header.column.getCanSort() && (
218
+ <button
219
+ onClick={header.column.getToggleSortingHandler()}
220
+ >
221
+ {{
222
+ asc: (
223
+ <Icon
224
+ icon={<MdExpandMore fontSize={20} />}
225
+ size="medium"
226
+ />
227
+ ),
228
+ desc: (
229
+ <Icon
230
+ icon={<MdExpandLess fontSize={20} />}
231
+ size="medium"
232
+ />
233
+ ),
234
+ }[header.column.getIsSorted() as string] ?? (
235
+ <Icon
236
+ icon={<MdUnfoldMore fontSize={22} />}
237
+ size="medium"
238
+ />
239
+ )}
240
+ </button>
241
+ )}
242
+ {flexRender(
243
+ header.column.columnDef.header,
244
+ header.getContext()
245
+ )}
246
+ {filterable && header.column.getCanFilter() && (
247
+ <Filter column={header.column} table={table} />
248
+ )}
249
+ </Track>
250
+ )}
251
+ </th>
252
+ ))}
253
+ </tr>
254
+ ))}
255
+ </thead>
256
+ )}
257
+ <tbody>
258
+ {tableBodyPrefix}
259
+ {table.getRowModel().rows.map((row) => (
260
+ <tr
261
+ key={row.id}
262
+ style={table.options.meta?.getRowStyles(row)}
263
+ className={selectedRow?.(row) ? 'highlighted' : 'default'}
264
+ >
265
+ {row.getVisibleCells().map((cell) => (
266
+ <td
267
+ key={cell.id}
268
+ className={selectedRow?.(row) ? 'highlighted' : 'default'}
269
+ style={{
270
+ position: cell.column.columnDef.meta?.sticky
271
+ ? 'sticky'
272
+ : undefined,
273
+ left:
274
+ cell.column.columnDef.meta?.sticky === 'left'
275
+ ? `${cell.column.getAfter('left') * 0.675}px`
276
+ : undefined,
277
+ right:
278
+ cell.column.columnDef.meta?.sticky === 'right'
279
+ ? `${cell.column.getAfter('right') * 0.675}px`
280
+ : undefined,
281
+ zIndex: cell.column.columnDef.meta?.sticky ? 1 : 0,
282
+ }}
283
+ >
284
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
285
+ </td>
286
+ ))}
287
+ </tr>
288
+ ))}
289
+ </tbody>
290
+ </table>
291
+ </div>
292
+ {pagination && (
293
+ <div className="data-table__pagination-wrapper">
294
+ {table.getPageCount() * table.getState().pagination.pageSize >
295
+ table.getState().pagination.pageSize && (
296
+ <div className="data-table__pagination">
297
+ <button
298
+ className="previous"
299
+ onClick={() => {
300
+ table.previousPage();
301
+ setSearchParams((params) => {
302
+ params.set(
303
+ 'page',
304
+ String(Number(searchParams.get('page') ?? '1') - 1)
305
+ );
306
+ return params;
307
+ });
308
+ }}
309
+ disabled={!table.getCanPreviousPage()}
310
+ >
311
+ <MdOutlineWest />
312
+ </button>
313
+ <nav
314
+ role="navigation"
315
+ aria-label={t('global.paginationNavigation') ?? ''}
316
+ >
317
+ <ul className="links">
318
+ {pageIndexes[0] > 0 && (
319
+ <>
320
+ <li key={`${id}-0`}>
321
+ <Link
322
+ to={`?page=1&${searchParamsWithoutPage.toString()}`}
323
+ onClick={() => {
324
+ table.setPageIndex(0);
325
+ setSearchParams((params) => {
326
+ params.set('page', '1');
327
+ return params;
328
+ });
329
+ }}
330
+ aria-label={t('global.gotoPage') + 0}
331
+ aria-current={
332
+ table.getState().pagination.pageIndex === 0
333
+ }
334
+ >
335
+ 1
336
+ </Link>
337
+ </li>
338
+ {pageIndexes[0] > 1 && <li>...</li>}
339
+ </>
340
+ )}
341
+ {pageIndexes.map((index) => (
342
+ <li
343
+ key={`${id}-${index}`}
344
+ className={clsx({
345
+ active: table.getState().pagination.pageIndex === index,
346
+ })}
347
+ >
348
+ <Link
349
+ to={`?page=${
350
+ index + 1
351
+ }&${searchParamsWithoutPage.toString()}`}
352
+ onClick={() => table.setPageIndex(index)}
353
+ aria-label={t('global.gotoPage') + index}
354
+ aria-current={
355
+ table.getState().pagination.pageIndex === index
356
+ }
357
+ >
358
+ {index + 1}
359
+ </Link>
360
+ </li>
361
+ ))}
362
+ {pageIndexes[pageIndexes.length - 1] <
363
+ table.getPageCount() - 1 && (
364
+ <>
365
+ {pageIndexes[pageIndexes.length - 1] <
366
+ table.getPageCount() - 2 && <li>...</li>}
367
+ <li key={`${id}-${table.getPageCount() - 1}`}>
368
+ <Link
369
+ to={`?page=${table.getPageCount()}&${searchParamsWithoutPage.toString()}`}
370
+ onClick={() => {
371
+ table.setPageIndex(table.getPageCount() - 1);
372
+ setSearchParams((params) => {
373
+ params.set('page', '' + table.getPageCount());
374
+ return params;
375
+ });
376
+ }}
377
+ aria-label={
378
+ t('global.gotoPage') + (table.getPageCount() - 1)
379
+ }
380
+ aria-current={
381
+ table.getState().pagination.pageIndex ===
382
+ table.getPageCount() - 1
383
+ }
384
+ >
385
+ {table.getPageCount()}
386
+ </Link>
387
+ </li>
388
+ </>
389
+ )}
390
+ </ul>
391
+ </nav>
392
+ <button
393
+ className="next"
394
+ onClick={() => {
395
+ table.nextPage();
396
+ setSearchParams((params) => {
397
+ params.set(
398
+ 'page',
399
+ String(Number(searchParams.get('page') ?? '1') + 1)
400
+ );
401
+ return params;
402
+ });
403
+ }}
404
+ disabled={!table.getCanNextPage()}
405
+ >
406
+ <MdOutlineEast />
407
+ </button>
408
+ </div>
409
+ )}
410
+ <div className="data-table__page-size">
411
+ <label htmlFor={id}>{t('global.resultCount')}</label>
412
+ <select
413
+ id={id}
414
+ value={table.getState().pagination.pageSize}
415
+ onChange={(e) => {
416
+ table.setPageSize(Number(e.target.value));
417
+ }}
418
+ >
419
+ {[10, 20, 30, 40, 50].map((pageSize) => (
420
+ <option key={pageSize} value={pageSize}>
421
+ {pageSize}
422
+ </option>
423
+ ))}
424
+ </select>
425
+ </div>
426
+ </div>
427
+ )}
428
+ </>
429
+ );
430
+ };
431
+
432
+ export default DataTable;
@@ -0,0 +1,63 @@
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
+ .dialog {
7
+ background-color: get-color(white);
8
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.25);
9
+ border-radius: 4px;
10
+ position: fixed;
11
+ top: 50%;
12
+ left: 50%;
13
+ transform: translate(-50%, -50%);
14
+ width: 100%;
15
+ max-width: 600px;
16
+ z-index: 101;
17
+ max-height: 90vh;
18
+
19
+ &--large {
20
+ max-width: 800px;
21
+ }
22
+
23
+ &__overlay {
24
+ position: fixed;
25
+ inset: 0;
26
+ background-color: rgba(0, 0, 0, 0.54);
27
+ z-index: 100;
28
+ }
29
+
30
+ &__header,
31
+ &__body,
32
+ &__footer {
33
+ padding: get-spacing(haapsalu);
34
+ }
35
+
36
+ &__header {
37
+ display: flex;
38
+ align-items: center;
39
+ gap: get-spacing(haapsalu);
40
+ background-color: get-color(black-coral-0);
41
+ border-bottom: 1px solid get-color(black-coral-2);
42
+ }
43
+
44
+ &__title {
45
+ flex: 1;
46
+ }
47
+
48
+ &__close {
49
+ display: flex;
50
+ align-items: center;
51
+ justify-content: center;
52
+ font-size: 20px;
53
+ }
54
+
55
+ &__body {
56
+ overflow: auto;
57
+ max-height: calc(90vh - 70px);
58
+ }
59
+
60
+ &__footer {
61
+ border-top: 1px solid get-color(black-coral-2);
62
+ }
63
+ }
@@ -0,0 +1,44 @@
1
+ import React, { FC, PropsWithChildren, ReactNode } from 'react';
2
+ import * as RadixDialog from '@radix-ui/react-dialog';
3
+ import { MdOutlineClose } from 'react-icons/md';
4
+ import clsx from 'clsx';
5
+
6
+ import { Icon, Track } from '../';
7
+ import './Dialog.scss';
8
+
9
+ type DialogProps = {
10
+ title?: string | null;
11
+ footer?: ReactNode;
12
+ onClose: () => void;
13
+ size?: 'default' | 'large';
14
+ }
15
+
16
+ const Dialog: FC<PropsWithChildren<DialogProps>> = ({ title, footer, onClose, size = 'default', children }) => {
17
+ return (
18
+ <RadixDialog.Root defaultOpen={true} onOpenChange={onClose}>
19
+ <RadixDialog.Portal>
20
+ <RadixDialog.Overlay className='dialog__overlay' />
21
+ <RadixDialog.Content className={clsx('dialog', `dialog--${size}`)}>
22
+ {
23
+ title && <div className='dialog__header'>
24
+ <RadixDialog.Title className='h3 dialog__title'>{title}</RadixDialog.Title>
25
+ <RadixDialog.Close asChild>
26
+ <button className='dialog__close'>
27
+ <Icon icon={<MdOutlineClose />} size='medium' />
28
+ </button>
29
+ </RadixDialog.Close>
30
+ </div>
31
+ }
32
+ <div className='dialog__body'>
33
+ {children}
34
+ </div>
35
+ {footer && (
36
+ <Track className='dialog__footer' gap={16} justify='end'>{footer}</Track>
37
+ )}
38
+ </RadixDialog.Content>
39
+ </RadixDialog.Portal>
40
+ </RadixDialog.Root>
41
+ );
42
+ };
43
+
44
+ export default Dialog;
@@ -0,0 +1,40 @@
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
+ .drawer {
7
+ position: fixed;
8
+ display: flex;
9
+ flex-direction: column;
10
+ top: 100px;
11
+ right: 0;
12
+ bottom: 0;
13
+ background-color: get-color(white);
14
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.14);
15
+ width: 50%;
16
+ transition: transform .25s ease-out;
17
+ overflow: hidden;
18
+ z-index: 98;
19
+
20
+ &__header {
21
+ display: flex;
22
+ align-items: center;
23
+ gap: get-spacing(haapsalu);
24
+ padding: get-spacing(haapsalu);
25
+ border-bottom: 1px solid get-color(black-coral-2);
26
+
27
+ .icon {
28
+ font-size: 20px;
29
+ }
30
+ }
31
+
32
+ &__title,
33
+ &__body {
34
+ flex: 1;
35
+ }
36
+
37
+ &__body {
38
+ overflow: auto;
39
+ }
40
+ }
@@ -0,0 +1,42 @@
1
+ import React, { CSSProperties, FC, PropsWithChildren, useEffect, useRef } from 'react';
2
+ import { MdOutlineClose } from 'react-icons/md';
3
+ import autoAnimate from '@formkit/auto-animate';
4
+
5
+ import { Icon } from '../';
6
+ import './Drawer.scss';
7
+
8
+ type DrawerProps = {
9
+ title: string;
10
+ onClose: () => void;
11
+ style?: CSSProperties;
12
+ }
13
+
14
+ const Drawer: FC<PropsWithChildren<DrawerProps>> = ({ title, onClose, children, style }) => {
15
+ const ref = useRef<HTMLDivElement>(null);
16
+
17
+ useEffect(() => {
18
+ ref.current && autoAnimate(ref.current);
19
+ const handleKeyup = (e: KeyboardEvent) => {
20
+ if (e.key === 'Escape') onClose();
21
+ };
22
+ document.addEventListener('keyup', handleKeyup);
23
+
24
+ return () => document.removeEventListener('keyup', handleKeyup);
25
+ }, [onClose]);
26
+
27
+ return (
28
+ <div className='drawer' style={style}>
29
+ <div className='drawer__header'>
30
+ <h2 className='h3 drawer__title'>{title}</h2>
31
+ <button className='drawer__close' onClick={onClose}>
32
+ <Icon icon={<MdOutlineClose />} size='medium' />
33
+ </button>
34
+ </div>
35
+ <div className='drawer__body'>
36
+ {children}
37
+ </div>
38
+ </div>
39
+ );
40
+ };
41
+
42
+ export default Drawer;
@@ -0,0 +1,57 @@
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
+ .checkbox {
7
+ width: 100%;
8
+ display: flex;
9
+ align-items: flex-start;
10
+ gap: get-spacing(paldiski);
11
+
12
+ &__label {
13
+ display: block;
14
+ flex: 0 0 185px;
15
+ font-size: $veera-font-size-100;
16
+ line-height: 24px;
17
+ }
18
+
19
+ &__item {
20
+ input[type=checkbox] {
21
+ display: none;
22
+
23
+ + label {
24
+ display: block;
25
+ padding-left: 32px;
26
+ position: relative;
27
+ font-size: $veera-font-size-100;
28
+ line-height: $veera-line-height-500;
29
+
30
+ &::before {
31
+ content: '';
32
+ display: block;
33
+ width: 16px;
34
+ height: 16px;
35
+ box-shadow: inset 0 0 0 1px get-color(black-coral-2);
36
+ border-radius: 2px;
37
+ position: absolute;
38
+ left: 4px;
39
+ top: 4px;
40
+ }
41
+ }
42
+
43
+ &:checked {
44
+ + label {
45
+ &::before {
46
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTQiIGhlaWdodD0iMTEiIHZpZXdCb3g9IjAgMCAxNCAxMSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTQuNzQ5NzkgOC4xMjkwNkwxLjYyMjI5IDUuMDAxNTZMMC41NjEwMzUgNi4wNjI4MUw0Ljc0OTc5IDEwLjI1MTZMMTMuNzQ5OCAxLjI1MTU2TDEyLjY4ODUgMC4xOTAzMDhMNC43NDk3OSA4LjEyOTA2WiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg==');
47
+ background-color: get-color(sapphire-blue-10);
48
+ background-repeat: no-repeat;
49
+ background-position: center;
50
+ background-size: 13px 10px;
51
+ box-shadow: inset 0 0 0 1px get-color(sapphire-blue-10);
52
+ }
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }