tycho-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 (67) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc.cjs +28 -0
  3. package/.eslintrc.json +31 -0
  4. package/.gitlab-ci.yml +14 -0
  5. package/.storybook/main.ts +32 -0
  6. package/.storybook/preview-head.html +4 -0
  7. package/.storybook/preview.css +6 -0
  8. package/.storybook/preview.tsx +29 -0
  9. package/README.md +93 -0
  10. package/package.json +66 -0
  11. package/src/AppColorpicker/AppColorpicker.tsx +69 -0
  12. package/src/AppColorpicker/index.tsx +3 -0
  13. package/src/AppColorpicker/style.scss +38 -0
  14. package/src/AppEditable/AppEditable.tsx +280 -0
  15. package/src/AppEditable/AppEditableField.ts +7 -0
  16. package/src/AppEditable/FormField.ts +26 -0
  17. package/src/AppEditable/FormFieldOption.ts +38 -0
  18. package/src/AppEditable/index.tsx +3 -0
  19. package/src/AppEditable/style.scss +94 -0
  20. package/src/AppModal/AppModal.tsx +93 -0
  21. package/src/AppModal/AppModalConfirm.tsx +62 -0
  22. package/src/AppModal/AppModalRemove.tsx +51 -0
  23. package/src/AppModal/index.tsx +3 -0
  24. package/src/AppModal/style.scss +65 -0
  25. package/src/AppToast/AppToast.tsx +94 -0
  26. package/src/AppToast/ToastMessage.ts +9 -0
  27. package/src/AppToast/index.tsx +3 -0
  28. package/src/AppToast/style.scss +0 -0
  29. package/src/Dummy/Dummy.stories.tsx +21 -0
  30. package/src/Dummy/Dummy.tsx +16 -0
  31. package/src/Dummy/index.tsx +3 -0
  32. package/src/Dummy/styles.scss +6 -0
  33. package/src/Participants/ParticipantCreate/ParticipantCreate.tsx +86 -0
  34. package/src/Participants/ParticipantCreate/index.tsx +3 -0
  35. package/src/Participants/ParticipantCreate/style.scss +32 -0
  36. package/src/Participants/ParticipantRemove/ParticipantRemove.tsx +51 -0
  37. package/src/Participants/ParticipantRemove/index.tsx +3 -0
  38. package/src/Participants/ParticipantRemove/style.scss +32 -0
  39. package/src/Participants/Participants.stories.tsx +45 -0
  40. package/src/Participants/Participants.tsx +145 -0
  41. package/src/Participants/index.tsx +3 -0
  42. package/src/Participants/style.scss +44 -0
  43. package/src/Participants/types/Participant.ts +43 -0
  44. package/src/Participants/types/ParticipantService.ts +18 -0
  45. package/src/configs/CommonContext.tsx +23 -0
  46. package/src/configs/CookieStorage.ts +36 -0
  47. package/src/configs/Localization.ts +28 -0
  48. package/src/configs/MessageUtils.ts +60 -0
  49. package/src/configs/Storage.ts +21 -0
  50. package/src/configs/api.ts +49 -0
  51. package/src/configs/localization/CommonTexts.ts +26 -0
  52. package/src/configs/localization/ParticipantsTexts.ts +40 -0
  53. package/src/configs/store/actions.ts +12 -0
  54. package/src/configs/store/reducer.ts +22 -0
  55. package/src/configs/store/store.ts +9 -0
  56. package/src/configs/store/types.ts +16 -0
  57. package/src/index.ts +4 -0
  58. package/src/react-app-env.d.ts +5 -0
  59. package/src/styles/_variables.scss +67 -0
  60. package/src/styles/bootstrap.min.css +9871 -0
  61. package/src/styles/main.scss +57 -0
  62. package/src/vite-env.d.ts +13 -0
  63. package/stories/Configure.mdx +171 -0
  64. package/stories/StorybookUtils.tsx +40 -0
  65. package/tsconfig.json +31 -0
  66. package/tsconfig.node.json +10 -0
  67. package/vite.config.ts +30 -0
@@ -0,0 +1,280 @@
1
+ import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
2
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3
+ import { format } from 'date-fns-tz';
4
+ import EasyEdit from 'react-easy-edit';
5
+ import { useTranslation } from 'react-i18next';
6
+ import Switch from 'react-switch';
7
+ import AppColorpicker from '../AppColorpicker';
8
+ import AppEditableField from './AppEditableField';
9
+ import FormField from './FormField';
10
+ import FormFieldOption, { FieldValue } from './FormFieldOption';
11
+ import './style.scss';
12
+
13
+ type ItemValue = unknown;
14
+ type Item = {
15
+ [key: string]: ItemValue | Item;
16
+ };
17
+
18
+ type Props = {
19
+ translation: string;
20
+ item: Record<string, unknown>;
21
+ fields: FormField[];
22
+ save?: (field: AppEditableField) => void;
23
+ group?: string;
24
+ className?: string;
25
+ reference?: string;
26
+ };
27
+
28
+ export default function AppEditable({
29
+ translation,
30
+ fields,
31
+ item,
32
+ save,
33
+ group,
34
+ className,
35
+ reference,
36
+ }: Props) {
37
+ const { t } = useTranslation([translation, 'common']);
38
+
39
+ const formatDate = (date: Date | null, fmt: string) => {
40
+ if (!date) return '';
41
+ return format(date, fmt);
42
+ };
43
+
44
+ const handleSave = (value: FieldValue, field: FormField) => {
45
+ save &&
46
+ save({
47
+ uid: item.uid as string,
48
+ name: field.name,
49
+ type: field.type,
50
+ value,
51
+ ref: reference ? (item[reference] as string) : undefined,
52
+ });
53
+ };
54
+
55
+ const getDateValue = (field: FormField) => {
56
+ const v = getValue(field) as string;
57
+ return !v ? null : formatDate(new Date(v), 'yyyy-MM-dd');
58
+ };
59
+
60
+ const getValue = (field: FormField): unknown => {
61
+ if (field.name.indexOf('.') === -1) {
62
+ if (!isEmptyValue(item, field.name)) return item[field.name];
63
+ return field.default !== undefined ? field.default : null;
64
+ }
65
+
66
+ const splits = field.name.split('.');
67
+ let val: unknown = item;
68
+ for (const split of splits) {
69
+ if (typeof val === 'object' && val !== null && split in val) {
70
+ val = (val as Item)[split];
71
+ } else {
72
+ val = undefined;
73
+ break;
74
+ }
75
+ }
76
+
77
+ if (val !== undefined && val !== '') return val;
78
+ return field.default !== undefined ? field.default : null;
79
+ };
80
+
81
+ const isEmptyValue = (thisItem: Item, thisName: string) =>
82
+ thisItem[thisName] === undefined || thisItem[thisName] === '';
83
+
84
+ const getValueToDisplay = (field: FormField): React.ReactNode => {
85
+ const value = getValue(field);
86
+
87
+ if (field.options && field.options.length > 0) {
88
+ const option = field.options.find((op) => op.value === value);
89
+ if (option) return option.label;
90
+ }
91
+
92
+ // Fallback — if value is a string, number, or boolean, display it.
93
+ if (
94
+ typeof value === 'string' ||
95
+ typeof value === 'number' ||
96
+ typeof value === 'boolean'
97
+ ) {
98
+ return value;
99
+ }
100
+
101
+ return null;
102
+ };
103
+
104
+ const getLabelValueForList = (options: FormFieldOption[]) => {
105
+ return options.map((op) => ({
106
+ label: t(op.label),
107
+ value: op.value,
108
+ }));
109
+ };
110
+
111
+ return (
112
+ <>
113
+ {fields.map((field: FormField, idx: number) => (
114
+ <div
115
+ className={`editable-container ${className || ''}`}
116
+ key={idx.valueOf()}
117
+ >
118
+ <div className="title">
119
+ <h6>
120
+ {field.title
121
+ ? field.title
122
+ : t(`${translation}:${group || ''}.field.${field.name}`)}
123
+ {field.required && <span>*</span>}
124
+ </h6>
125
+
126
+ {field.tooltip && (
127
+ <FontAwesomeIcon
128
+ icon={faQuestionCircle}
129
+ className="info"
130
+ title={field.tooltip}
131
+ />
132
+ )}
133
+ </div>
134
+
135
+ {field.type === 'display' ? (
136
+ <span>{getValueToDisplay(field) ?? '--'}</span>
137
+ ) : null}
138
+
139
+ {field.type === 'code' ? (
140
+ <div className="textarea-code">
141
+ <EasyEdit
142
+ type="textarea"
143
+ onSave={(value: string) =>
144
+ handleSave(JSON.parse(value) as FieldValue, field)
145
+ }
146
+ saveButtonLabel={t('common:button.confirm')}
147
+ cancelButtonLabel={t('common:button.cancel')}
148
+ value={JSON.stringify(getValue(field) || {})}
149
+ placeholder={t('common:placeholder.input')}
150
+ />
151
+ </div>
152
+ ) : null}
153
+
154
+ {(field.type === 'text' ||
155
+ field.type === 'number' ||
156
+ field.type === 'locale' ||
157
+ field.type === 'year') && (
158
+ <EasyEdit
159
+ type="text"
160
+ onSave={(value: string) => handleSave(value, field)}
161
+ saveButtonLabel={t('common:button.confirm')}
162
+ cancelButtonLabel={t('common:button.cancel')}
163
+ value={getValue(field)}
164
+ placeholder={t('common:placeholder.input')}
165
+ />
166
+ )}
167
+
168
+ {field.type === 'textarea' ? (
169
+ <EasyEdit
170
+ type="textarea"
171
+ onSave={(value: string) => handleSave(value, field)}
172
+ saveButtonLabel={t('common:button.confirm')}
173
+ cancelButtonLabel={t('common:button.cancel')}
174
+ value={getValue(field)}
175
+ placeholder={t('common:placeholder.input')}
176
+ />
177
+ ) : null}
178
+
179
+ {field.type === 'select' || field.type === 'list' ? (
180
+ <EasyEdit
181
+ type="select"
182
+ options={getLabelValueForList(field.options || [])}
183
+ onSave={(value: string) => handleSave(value, field)}
184
+ saveButtonLabel={t('common:button.confirm')}
185
+ cancelButtonLabel={t('common:button.cancel')}
186
+ placeholder={t('common:placeholder.input')}
187
+ value={getValue(field)}
188
+ />
189
+ ) : null}
190
+
191
+ {field.type === 'range' ? (
192
+ <EasyEdit
193
+ type="range"
194
+ onSave={(value: string) => handleSave(value, field)}
195
+ attributes={{ name: 'awesome-range', min: 0, max: 100, step: 1 }}
196
+ placeholder={t('common:placeholder.input')}
197
+ value={getValue(field)}
198
+ saveButtonLabel={t('common:button.confirm')}
199
+ cancelButtonLabel={t('common:button.cancel')}
200
+ />
201
+ ) : null}
202
+
203
+ {field.type === 'switch' ? (
204
+ <Switch
205
+ onChange={(checked: boolean) => handleSave(checked, field)}
206
+ checked={(getValue(field) as boolean) || false}
207
+ />
208
+ ) : null}
209
+
210
+ {field.type === 'date' && (
211
+ <EasyEdit
212
+ type="date"
213
+ onSave={(value: string) =>
214
+ handleSave(new Date(value) as FieldValue, field)
215
+ }
216
+ placeholder={t('common:placeholder.input')}
217
+ value={getDateValue(field)}
218
+ saveButtonLabel={t('common:button.confirm')}
219
+ cancelButtonLabel={t('common:button.cancel')}
220
+ />
221
+ )}
222
+
223
+ {field.type === 'password' && (
224
+ <EasyEdit
225
+ type="password"
226
+ onSave={(value: string) => handleSave(value, field)}
227
+ saveButtonLabel={t('common:button.confirm')}
228
+ cancelButtonLabel={t('common:button.cancel')}
229
+ value={getValue(field)}
230
+ placeholder={t('common:placeholder.input')}
231
+ />
232
+ )}
233
+
234
+ {field.type === 'color' ? (
235
+ <AppColorpicker item={item} field={field} handleSave={handleSave} />
236
+ ) : null}
237
+ </div>
238
+ ))}
239
+
240
+ {/*
241
+ <h3>Radio Buttons</h3>
242
+ <EasyEdit
243
+ type={Types.RADIO}
244
+ value="one"
245
+ onSave={save}
246
+ options={generateOptionsList()}
247
+ instructions="Custom instructions"
248
+ />
249
+ <h3>DateTime</h3>
250
+ <EasyEdit
251
+ type={Types.DATETIME_LOCAL}
252
+ onSave={save}
253
+ instructions="Select your date and time of birth"
254
+ />
255
+ <h3>Dropdown</h3>
256
+ <EasyEdit
257
+ type={Types.SELECT}
258
+ options={generateOptionsList()}
259
+ onSave={save}
260
+ placeholder={t('common:placeholder.input')}
261
+ instructions="Custom instructions"
262
+ />
263
+ <h3>Datalist</h3>
264
+ <EasyEdit
265
+ type={Types.DATALIST}
266
+ options={generateOptionsList()}
267
+ onSave={save}
268
+ instructions="Start typing to get suggestions"
269
+ />
270
+ <h3>Checkboxes</h3>
271
+ <EasyEdit
272
+ type={Types.CHECKBOX}
273
+ options={generateOptionsList()}
274
+ onSave={save}
275
+ value={['one', 'two']} // this will preselect two options
276
+ />
277
+ */}
278
+ </>
279
+ );
280
+ }
@@ -0,0 +1,7 @@
1
+ export default interface AppEditableField {
2
+ uid: string;
3
+ name: string;
4
+ value: any;
5
+ type?: string;
6
+ ref?: string;
7
+ }
@@ -0,0 +1,26 @@
1
+ import FormFieldOption from './FormFieldOption';
2
+
3
+ export type FieldOperations = 'create' | 'update' | 'delete';
4
+
5
+ export default interface FormField {
6
+ name: string;
7
+ title?: string;
8
+ type: string;
9
+ required?: boolean;
10
+ disabled?: boolean;
11
+ options?: FormFieldOption[];
12
+ tooltip?: string;
13
+ default?: string | boolean | number;
14
+ role?: string;
15
+ }
16
+
17
+ export const validateFormField = (
18
+ entity: Record<string, unknown>,
19
+ fields: FormField[]
20
+ ) => {
21
+ const invalids = [];
22
+ for (const field of fields) {
23
+ if (field.required && !entity[field.name]) invalids.push(field);
24
+ }
25
+ return invalids;
26
+ };
@@ -0,0 +1,38 @@
1
+ export type FieldValue = string | number | boolean | Date | null;
2
+
3
+ export default interface FormFieldOption {
4
+ label: string;
5
+ value: FieldValue;
6
+ }
7
+
8
+ export const convertEnum = (
9
+ values: Record<string, string | number | boolean>
10
+ ): FormFieldOption[] => {
11
+ let fields: FormFieldOption[] = [{ label: '', value: '' }];
12
+
13
+ Object.keys(values).forEach((key) => {
14
+ fields = [...fields, { label: String(values[key]), value: key }];
15
+ });
16
+
17
+ return fields;
18
+ };
19
+
20
+ export const convertList = (
21
+ values: Array<Record<string, unknown>>,
22
+ keyAttr: string,
23
+ valueAttr: string
24
+ ): FormFieldOption[] => {
25
+ let fields: FormFieldOption[] = [{ label: '', value: '' }];
26
+
27
+ values.forEach((value) => {
28
+ fields = [
29
+ ...fields,
30
+ {
31
+ label: String(value[keyAttr]),
32
+ value: value[valueAttr] as FieldValue,
33
+ },
34
+ ];
35
+ });
36
+
37
+ return fields;
38
+ };
@@ -0,0 +1,3 @@
1
+ import AppEditable from './AppEditable';
2
+
3
+ export default AppEditable;
@@ -0,0 +1,94 @@
1
+ .editable-container {
2
+ margin-top: var(--spacing-small);
3
+
4
+ .title {
5
+ display: flex;
6
+ align-items: center;
7
+ margin-bottom: var(--spacing-xsmall);
8
+
9
+ h6 {
10
+ margin-bottom: 0px;
11
+ }
12
+
13
+ .info {
14
+ width: 16px;
15
+ height: 16px;
16
+ margin-left: var(--spacing-xsmall);
17
+ cursor: pointer;
18
+ }
19
+ }
20
+ }
21
+
22
+ .modal-map {
23
+ width: 300px;
24
+ height: 200px;
25
+ }
26
+
27
+ .easy-edit-wrapper {
28
+ cursor: pointer;
29
+ color: var(--color-primary);
30
+ text-decoration: underline;
31
+ text-decoration-style: dotted;
32
+ text-underline-offset: 5px;
33
+ }
34
+
35
+ .textarea-code {
36
+ .easy-edit-inline-wrapper .easy-edit-component-wrapper {
37
+ textarea {
38
+ font-family: "Courier New", Courier, monospace;
39
+ height: 300px;
40
+ }
41
+ }
42
+ }
43
+
44
+ .easy-edit-inline-wrapper {
45
+ .easy-edit-component-wrapper {
46
+ width: 100%;
47
+ display: block;
48
+ margin-bottom: var(--spacing-xsmall);
49
+
50
+ input,
51
+ textarea,
52
+ select {
53
+ width: 100%;
54
+ }
55
+
56
+ select {
57
+ padding: var(--spacing-xxsmall);
58
+ border-color: var(--color-disabled);
59
+ border-radius: var(--spacing-xxsmall);
60
+ }
61
+ }
62
+
63
+ .easy-edit-checkbox-label {
64
+ cursor: pointer;
65
+ font-size: var(--font-size-body);
66
+ margin-bottom: var(--spacing-xxsmall);
67
+
68
+ input[type="checkbox"] {
69
+ margin-right: 8px;
70
+ }
71
+ }
72
+
73
+ .easy-edit-button-wrapper {
74
+ width: 100%;
75
+ display: flex;
76
+ justify-content: end;
77
+ margin-top: 16px;
78
+
79
+ .easy-edit-button {
80
+ padding-right: var(--spacing-xsmall);
81
+ padding-left: var(--spacing-xsmall);
82
+
83
+ &[name="save"] {
84
+ background-color: var(--color-confirmed);
85
+ color: var(--color-surface);
86
+ }
87
+
88
+ &[name="cancel"] {
89
+ background-color: var(--color-error);
90
+ color: var(--color-surface);
91
+ }
92
+ }
93
+ }
94
+ }
@@ -0,0 +1,93 @@
1
+ import { Box, Fade, Modal } from '@mui/material';
2
+ import cx from 'classnames';
3
+ import React from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Button, Icon } from 'tycho-storybook';
6
+ import './style.scss';
7
+
8
+ type Props = {
9
+ children: React.ReactNode;
10
+ title: string;
11
+ close: () => void;
12
+ confirm?: () => void;
13
+ cancel?: () => void;
14
+ subtitle?: string;
15
+ className?: string;
16
+ disableConfirm?: boolean;
17
+ hideFooter?: boolean;
18
+ disableCancel?: boolean;
19
+ disableClose?: boolean;
20
+ confirmLabel?: string;
21
+ closeLabel?: string;
22
+ onEntered?: () => void;
23
+ };
24
+
25
+ export default function AppModal({
26
+ children,
27
+ title,
28
+ subtitle,
29
+ className,
30
+ close,
31
+ confirm,
32
+ disableConfirm,
33
+ hideFooter,
34
+ disableClose,
35
+ disableCancel,
36
+ confirmLabel,
37
+ closeLabel,
38
+ cancel,
39
+ onEntered,
40
+ }: Props) {
41
+ const { t } = useTranslation('common');
42
+
43
+ const getClassNames = cx('modal-container', className);
44
+
45
+ return (
46
+ <Modal open>
47
+ <Fade in onEntered={() => onEntered && onEntered()}>
48
+ <Box className={getClassNames} sx={style}>
49
+ <div className="header">
50
+ <div className="titles">
51
+ <span className="title">{title}</span>
52
+ {subtitle && <span className="subtitle">{subtitle}</span>}
53
+ </div>
54
+ {!disableClose && (
55
+ <Icon name="close" onClick={close} className="pointer" />
56
+ )}
57
+ </div>
58
+ <div className="body">{children}</div>
59
+ {!hideFooter ? (
60
+ <div className="footer">
61
+ {!disableCancel && (
62
+ <Button
63
+ onClick={cancel || close}
64
+ text={closeLabel || t('button.cancel')}
65
+ color="danger"
66
+ />
67
+ )}
68
+ {confirm && (
69
+ <Button
70
+ onClick={confirm}
71
+ disabled={disableConfirm}
72
+ text={confirmLabel || t('button.confirm')}
73
+ />
74
+ )}
75
+ </div>
76
+ ) : null}
77
+ </Box>
78
+ </Fade>
79
+ </Modal>
80
+ );
81
+ }
82
+
83
+ const style = {
84
+ position: 'absolute',
85
+ top: '40%',
86
+ left: '50%',
87
+ transform: 'translate(-50%, -50%)',
88
+ width: '50%',
89
+ maxWidth: '1080px',
90
+ borderRadius: 'var(--radius-200)',
91
+ boxShadow: 24,
92
+ bgcolor: 'var(--background-default)',
93
+ };
@@ -0,0 +1,62 @@
1
+ import { Box, Modal } from '@mui/material';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button, Icon } from 'tycho-storybook';
4
+ import './style.scss';
5
+
6
+ type Props = {
7
+ title: string;
8
+ subtitle: string;
9
+ onClose: () => void;
10
+ onConfirm: () => void;
11
+ closeLabel?: string;
12
+ confirmLabel?: string;
13
+ };
14
+
15
+ export default function AppModalConfirm({
16
+ title,
17
+ subtitle,
18
+ onClose,
19
+ onConfirm,
20
+ closeLabel,
21
+ confirmLabel,
22
+ }: Props) {
23
+ const { t } = useTranslation('header');
24
+
25
+ return (
26
+ <Modal open>
27
+ <Box className="modal-container modal-remove" sx={style}>
28
+ <div className="body">
29
+ <Icon name="warning" size="large" filled />
30
+ <div className="texts">
31
+ <span className="title">{title}</span>
32
+ <span className="subtitle">{subtitle}</span>
33
+ </div>
34
+ </div>
35
+
36
+ <div className="footer">
37
+ <Button
38
+ onClick={onClose}
39
+ text={closeLabel || t('modal.button.cancel')}
40
+ mode="tonal"
41
+ />
42
+ <Button
43
+ onClick={onConfirm}
44
+ text={confirmLabel || t('modal.button.confirm')}
45
+ />
46
+ </div>
47
+ </Box>
48
+ </Modal>
49
+ );
50
+ }
51
+
52
+ const style = {
53
+ position: 'absolute',
54
+ top: '40%',
55
+ left: '50%',
56
+ transform: 'translate(-50%, -50%)',
57
+ width: '30%',
58
+ maxWidth: '580px',
59
+ borderRadius: 'var(--radius-200)',
60
+ boxShadow: 24,
61
+ bgcolor: 'var(--background-default)',
62
+ };
@@ -0,0 +1,51 @@
1
+ import { Box, Modal } from '@mui/material';
2
+ import { useTranslation } from 'react-i18next';
3
+ import { Button, Icon } from 'tycho-storybook';
4
+ import './style.scss';
5
+
6
+ type Props = {
7
+ title: string;
8
+ subtitle: string;
9
+ onClose: () => void;
10
+ onConfirm: () => void;
11
+ };
12
+
13
+ export default function AppModalRemove({
14
+ title,
15
+ subtitle,
16
+ onClose,
17
+ onConfirm,
18
+ }: Props) {
19
+ const { t } = useTranslation('common');
20
+
21
+ return (
22
+ <Modal open>
23
+ <Box className="modal-container modal-remove" sx={style}>
24
+ <div className="body">
25
+ <Icon name="warning" size="large" filled />
26
+ <div className="texts">
27
+ <span className="title">{title}</span>
28
+ <span className="subtitle">{subtitle}</span>
29
+ </div>
30
+ </div>
31
+
32
+ <div className="footer">
33
+ <Button onClick={onClose} text={t('button.cancel')} mode="tonal" />
34
+ <Button onClick={onConfirm} text={t('button.confirm')} />
35
+ </div>
36
+ </Box>
37
+ </Modal>
38
+ );
39
+ }
40
+
41
+ const style = {
42
+ position: 'absolute',
43
+ top: '40%',
44
+ left: '50%',
45
+ transform: 'translate(-50%, -50%)',
46
+ width: '30%',
47
+ maxWidth: '580px',
48
+ borderRadius: 'var(--radius-200)',
49
+ boxShadow: 24,
50
+ bgcolor: 'var(--background-default)',
51
+ };
@@ -0,0 +1,3 @@
1
+ import AppModal from './AppModal';
2
+
3
+ export default AppModal;