proje-react-panel 1.0.16 → 1.0.17-test-8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/components/FormField.d.ts +5 -1
- package/dist/components/components/InnerForm.d.ts +8 -3
- package/dist/components/components/Uploader.d.ts +8 -0
- package/dist/components/components/index.d.ts +1 -1
- package/dist/components/components/list/Datagrid.d.ts +3 -7
- package/dist/components/components/list/FilterPopup.d.ts +3 -3
- package/dist/components/components/list/ListPage.d.ts +2 -4
- package/dist/components/pages/FormPage.d.ts +8 -2
- package/dist/decorators/form/Input.d.ts +1 -0
- package/dist/decorators/list/List.d.ts +5 -6
- package/dist/decorators/list/ListData.d.ts +4 -4
- package/dist/decorators/list/getListFields.d.ts +2 -2
- package/dist/index.cjs.js +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/types/AnyClass.d.ts +1 -1
- package/dist/types/ScreenCreatorData.d.ts +5 -5
- package/package.json +1 -1
- package/src/components/components/FormField.tsx +15 -6
- package/src/components/components/InnerForm.tsx +47 -8
- package/src/components/components/Uploader.tsx +66 -0
- package/src/components/components/index.ts +2 -2
- package/src/components/components/list/Datagrid.tsx +91 -77
- package/src/components/components/list/FilterPopup.tsx +4 -4
- package/src/components/components/list/ListPage.tsx +13 -15
- package/src/components/pages/FormPage.tsx +8 -1
- package/src/decorators/form/Input.ts +3 -1
- package/src/decorators/form/getFormFields.ts +1 -0
- package/src/decorators/list/List.ts +8 -7
- package/src/decorators/list/ListData.ts +5 -5
- package/src/decorators/list/getListFields.ts +8 -8
- package/src/styles/form.scss +2 -4
- package/src/styles/index.scss +12 -12
- package/src/styles/list.scss +3 -1
- package/src/styles/uploader.scss +86 -0
- package/src/types/AnyClass.ts +3 -1
- package/src/types/ScreenCreatorData.ts +12 -12
- package/src/components/components/ImageUploader.tsx +0 -301
@@ -0,0 +1,66 @@
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
2
|
+
import { useForm, Controller, useFormContext } from 'react-hook-form';
|
3
|
+
import { InputOptions } from '../../decorators/form/Input';
|
4
|
+
|
5
|
+
interface UploaderProps {
|
6
|
+
input: InputOptions;
|
7
|
+
maxLength?: number;
|
8
|
+
}
|
9
|
+
|
10
|
+
export function Uploader({ input, maxLength = 1 }: UploaderProps) {
|
11
|
+
const form = useFormContext();
|
12
|
+
const [files, setFiles] = useState<File[]>([]);
|
13
|
+
const id = input.name!;
|
14
|
+
|
15
|
+
useEffect(() => {
|
16
|
+
// Update form value whenever files change
|
17
|
+
form.setValue(input.name + '_files', files.length > 0);
|
18
|
+
}, [files, form, input.name]);
|
19
|
+
|
20
|
+
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
21
|
+
if (maxLength > 1) {
|
22
|
+
throw new Error('TODO: Multiple file upload is not implemented yet');
|
23
|
+
}
|
24
|
+
|
25
|
+
const fileList = e.target.files;
|
26
|
+
if (fileList) {
|
27
|
+
const filesArray = Array.from(fileList);
|
28
|
+
setFiles(prevFiles => [...prevFiles, ...filesArray]);
|
29
|
+
}
|
30
|
+
};
|
31
|
+
|
32
|
+
const removeFile = (index: number) => {
|
33
|
+
setFiles(prevFiles => prevFiles.filter((_, i) => i !== index));
|
34
|
+
};
|
35
|
+
|
36
|
+
return (
|
37
|
+
<div className="uploader-container">
|
38
|
+
<button
|
39
|
+
type="button"
|
40
|
+
className="uploader-button"
|
41
|
+
onClick={() => document.getElementById(id)?.click()}
|
42
|
+
>
|
43
|
+
Upload Files
|
44
|
+
</button>
|
45
|
+
<input id={id} hidden name={input.name} onChange={onChange} type="file" multiple />
|
46
|
+
{files.length > 0 && (
|
47
|
+
<div className="uploader-files">
|
48
|
+
{files.map((file, index) => (
|
49
|
+
<div key={`${file.name}-${index}`} className="uploader-file">
|
50
|
+
<p>{file.name}</p>
|
51
|
+
<p>{(file.size / 1024 / 1024).toFixed(2)} MB</p>
|
52
|
+
<p>{file.type || 'Unknown type'}</p>
|
53
|
+
<button
|
54
|
+
onClick={() => removeFile(index)}
|
55
|
+
className="remove-file-button"
|
56
|
+
type="button"
|
57
|
+
>
|
58
|
+
Remove
|
59
|
+
</button>
|
60
|
+
</div>
|
61
|
+
))}
|
62
|
+
</div>
|
63
|
+
)}
|
64
|
+
</div>
|
65
|
+
);
|
66
|
+
}
|
@@ -2,7 +2,7 @@ export { InnerForm } from './InnerForm';
|
|
2
2
|
export { FormField } from './FormField';
|
3
3
|
export { LoadingScreen } from './LoadingScreen';
|
4
4
|
export { Counter } from './Counter';
|
5
|
-
export {
|
5
|
+
export { Uploader } from './Uploader';
|
6
6
|
export { ErrorComponent } from './ErrorComponent';
|
7
7
|
export { Label } from './Label';
|
8
|
-
export { ErrorBoundary } from './ErrorBoundary';
|
8
|
+
export { ErrorBoundary } from './ErrorBoundary';
|
@@ -9,15 +9,19 @@ import SearchIcon from '../../../assets/icons/svg/search.svg';
|
|
9
9
|
import PencilIcon from '../../../assets/icons/svg/pencil.svg';
|
10
10
|
import TrashIcon from '../../../assets/icons/svg/trash.svg';
|
11
11
|
|
12
|
-
interface DatagridProps<T
|
12
|
+
interface DatagridProps<T> {
|
13
13
|
data: T[];
|
14
|
-
listData: ListData
|
14
|
+
listData: ListData<T>;
|
15
15
|
onRemoveItem?: (item: T) => Promise<void>;
|
16
16
|
}
|
17
17
|
|
18
|
-
export function Datagrid<T
|
18
|
+
export function Datagrid<T>({ data, listData, onRemoveItem }: DatagridProps<T>) {
|
19
19
|
const cells = listData.cells;
|
20
|
-
const
|
20
|
+
const listGeneralCells = data?.[0]
|
21
|
+
? typeof listData.list?.cells === 'function'
|
22
|
+
? listData.list?.cells?.(data[0])
|
23
|
+
: listData.list?.cells
|
24
|
+
: null;
|
21
25
|
|
22
26
|
return (
|
23
27
|
<div className="datagrid">
|
@@ -30,89 +34,99 @@ export function Datagrid<T extends { id: string }>({ data, listData, onRemoveIte
|
|
30
34
|
{cells.map(cellOptions => (
|
31
35
|
<th key={cellOptions.name}>{cellOptions.title ?? cellOptions.name}</th>
|
32
36
|
))}
|
33
|
-
{
|
34
|
-
{
|
35
|
-
{
|
37
|
+
{listGeneralCells?.details && <th>Details</th>}
|
38
|
+
{listGeneralCells?.edit && <th>Edit</th>}
|
39
|
+
{listGeneralCells?.delete && <th>Delete</th>}
|
36
40
|
</tr>
|
37
41
|
</thead>
|
38
42
|
<tbody>
|
39
|
-
{data.map((item, index) =>
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
{data.map((item, index) => {
|
44
|
+
const listCells = item
|
45
|
+
? typeof listData.list?.cells === 'function'
|
46
|
+
? listData.list?.cells?.(item)
|
47
|
+
: listData.list?.cells
|
48
|
+
: null;
|
49
|
+
return (
|
50
|
+
<tr key={index}>
|
51
|
+
{cells.map(cellOptions => {
|
52
|
+
// @ts-ignore
|
53
|
+
const value = item[cellOptions.name];
|
54
|
+
let render = value ?? '-'; // Default value if the field is undefined or null
|
45
55
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
56
|
+
switch (cellOptions.type) {
|
57
|
+
case 'date':
|
58
|
+
if (value) {
|
59
|
+
const date = new Date(value);
|
60
|
+
render = `${date.getDate().toString().padStart(2, '0')}/${(
|
61
|
+
date.getMonth() + 1
|
62
|
+
)
|
63
|
+
.toString()
|
64
|
+
.padStart(
|
65
|
+
2,
|
66
|
+
'0'
|
67
|
+
)}/${date.getFullYear()} ${date.getHours().toString().padStart(2, '0')}:${date
|
68
|
+
.getMinutes()
|
69
|
+
.toString()
|
70
|
+
.padStart(2, '0')}`;
|
71
|
+
}
|
72
|
+
break;
|
63
73
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
74
|
+
case 'image': {
|
75
|
+
const imageCellOptions = cellOptions as ImageCellOptions;
|
76
|
+
render = (
|
77
|
+
<img
|
78
|
+
width={100}
|
79
|
+
height={100}
|
80
|
+
src={imageCellOptions.baseUrl + value}
|
81
|
+
style={{ objectFit: 'contain' }}
|
82
|
+
/>
|
83
|
+
);
|
84
|
+
break;
|
85
|
+
}
|
86
|
+
case 'string':
|
87
|
+
default:
|
88
|
+
render = value ? value.toString() : (cellOptions?.placeHolder ?? '-'); // Handles string type or default fallback
|
89
|
+
break;
|
75
90
|
}
|
76
|
-
|
77
|
-
default:
|
78
|
-
render = value ? value.toString() : (cellOptions?.placeHolder ?? '-'); // Handles string type or default fallback
|
79
|
-
break;
|
80
|
-
}
|
81
|
-
/*
|
91
|
+
/*
|
82
92
|
if (cellOptions.linkTo) {
|
83
93
|
render = <Link to={cellOptions.linkTo(item)}>{formattedValue}</Link>;
|
84
94
|
}
|
85
95
|
*/
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
96
|
+
return <td key={cellOptions.name}>{render}</td>;
|
97
|
+
})}
|
98
|
+
{listCells?.details && (
|
99
|
+
<td>
|
100
|
+
<Link to={listCells.details.path} className="util-cell-link">
|
101
|
+
<SearchIcon className="icon icon-search" />
|
102
|
+
<span className="util-cell-label">{listCells.details.label}</span>
|
103
|
+
</Link>
|
104
|
+
</td>
|
105
|
+
)}
|
106
|
+
{listCells?.edit && (
|
107
|
+
<td>
|
108
|
+
<Link to={listCells.edit.path} className="util-cell-link">
|
109
|
+
<PencilIcon className="icon icon-pencil" />
|
110
|
+
<span className="util-cell-label">{listCells.edit.label}</span>
|
111
|
+
</Link>
|
112
|
+
</td>
|
113
|
+
)}
|
114
|
+
{listCells?.delete && (
|
115
|
+
<td>
|
116
|
+
<a
|
117
|
+
onClick={() => {
|
118
|
+
onRemoveItem?.(item);
|
119
|
+
}}
|
120
|
+
className="util-cell-link util-cell-link-remove"
|
121
|
+
>
|
122
|
+
<TrashIcon className="icon icon-trash" />
|
123
|
+
<span className="util-cell-label">{listCells.delete.label}</span>
|
124
|
+
</a>
|
125
|
+
</td>
|
126
|
+
)}
|
127
|
+
</tr>
|
128
|
+
);
|
129
|
+
})}
|
116
130
|
</tbody>
|
117
131
|
</table>
|
118
132
|
)}
|
@@ -3,11 +3,11 @@ import { ListData } from '../../../decorators/list/ListData';
|
|
3
3
|
import { CellOptions, StaticSelectFilter } from '../../../decorators/list/Cell';
|
4
4
|
import Select from 'react-select';
|
5
5
|
|
6
|
-
interface FilterPopupProps {
|
6
|
+
interface FilterPopupProps<T> {
|
7
7
|
isOpen: boolean;
|
8
8
|
onClose: () => void;
|
9
9
|
onApplyFilters: (filters: Record<string, string>) => void;
|
10
|
-
listData: ListData
|
10
|
+
listData: ListData<T>;
|
11
11
|
activeFilters?: Record<string, string>;
|
12
12
|
}
|
13
13
|
|
@@ -126,13 +126,13 @@ function FilterField({ field, value, onChange }: FilterFieldProps): React.ReactE
|
|
126
126
|
}
|
127
127
|
}
|
128
128
|
|
129
|
-
export function FilterPopup({
|
129
|
+
export function FilterPopup<T>({
|
130
130
|
isOpen,
|
131
131
|
onClose,
|
132
132
|
onApplyFilters,
|
133
133
|
listData,
|
134
134
|
activeFilters,
|
135
|
-
}: FilterPopupProps): React.ReactElement | null {
|
135
|
+
}: FilterPopupProps<T>): React.ReactElement | null {
|
136
136
|
const [filters, setFilters] = React.useState<Record<string, any>>(activeFilters ?? {});
|
137
137
|
const popupRef = useRef<HTMLDivElement>(null);
|
138
138
|
const fields = useMemo(() => listData.cells.filter(cell => !!cell.filter), [listData.cells]);
|
@@ -26,17 +26,17 @@ export interface PaginatedResponse<T> {
|
|
26
26
|
|
27
27
|
export type GetDataForList<T> = (params: GetDataParams) => Promise<PaginatedResponse<T>>;
|
28
28
|
|
29
|
-
|
29
|
+
function ListHeader<T extends AnyClass>({
|
30
30
|
listData,
|
31
31
|
filtered,
|
32
32
|
onFilterClick,
|
33
33
|
customHeader,
|
34
34
|
}: {
|
35
|
-
listData: ListData
|
35
|
+
listData: ListData<T>;
|
36
36
|
filtered: boolean;
|
37
37
|
onFilterClick: () => void;
|
38
38
|
customHeader?: React.ReactNode;
|
39
|
-
})
|
39
|
+
}) {
|
40
40
|
const fields = useMemo(() => listData.cells.filter(cell => !!cell.filter), [listData.cells]);
|
41
41
|
|
42
42
|
const header = listData.list?.headers;
|
@@ -60,15 +60,15 @@ const ListHeader = ({
|
|
60
60
|
</div>
|
61
61
|
</div>
|
62
62
|
);
|
63
|
-
}
|
63
|
+
}
|
64
64
|
|
65
|
-
export function ListPage<T extends AnyClass
|
65
|
+
export function ListPage<T extends AnyClass>({
|
66
66
|
model,
|
67
67
|
getData,
|
68
68
|
onRemoveItem,
|
69
69
|
customHeader,
|
70
70
|
}: {
|
71
|
-
model:
|
71
|
+
model: any;
|
72
72
|
getData: GetDataForList<T>;
|
73
73
|
customHeader?: React.ReactNode;
|
74
74
|
onRemoveItem?: (item: T) => Promise<void>;
|
@@ -151,15 +151,13 @@ export function ListPage<T extends AnyClass & { id: string }>({
|
|
151
151
|
data={data}
|
152
152
|
onRemoveItem={async (item: T) => {
|
153
153
|
if (onRemoveItem) {
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
},
|
162
|
-
});
|
154
|
+
if (
|
155
|
+
confirm('Are you sure you want to delete this item? This action cannot be undone.')
|
156
|
+
) {
|
157
|
+
await onRemoveItem(item);
|
158
|
+
//setData(data.filter((d: T) => d.id !== item.id));
|
159
|
+
await fetchData(pagination.page);
|
160
|
+
}
|
163
161
|
}
|
164
162
|
}}
|
165
163
|
/>
|
@@ -2,16 +2,19 @@ import React, { useMemo } from 'react';
|
|
2
2
|
import { InnerForm } from '../components';
|
3
3
|
import { AnyClass } from '../../types/AnyClass';
|
4
4
|
import { getFormFields } from '../../decorators/form/getFormFields';
|
5
|
+
import { InputOptions } from '../../decorators/form/Input';
|
5
6
|
|
6
7
|
export type GetDetailsDataFN<T> = (param: Record<string, string>) => Promise<T>;
|
7
8
|
export type OnSubmitFN<T> = (data: T) => Promise<T>;
|
8
9
|
|
9
10
|
export interface FormPageProps<T extends AnyClass> {
|
10
|
-
model: T
|
11
|
+
model: any; //TODO: use T not typeof T
|
11
12
|
getDetailsData?: GetDetailsDataFN<T>;
|
12
13
|
redirect?: string;
|
13
14
|
onSubmit: OnSubmitFN<T>;
|
15
|
+
onSelectPreloader?: (inputOptions: InputOptions) => Promise<{ label: string; value: string }[]>;
|
14
16
|
redirectBackOnSuccess?: boolean;
|
17
|
+
type?: 'json' | 'formData';
|
15
18
|
}
|
16
19
|
|
17
20
|
export function FormPage<T extends AnyClass>({
|
@@ -19,7 +22,9 @@ export function FormPage<T extends AnyClass>({
|
|
19
22
|
getDetailsData,
|
20
23
|
onSubmit,
|
21
24
|
redirect,
|
25
|
+
onSelectPreloader,
|
22
26
|
redirectBackOnSuccess = true,
|
27
|
+
type = 'json',
|
23
28
|
...rest
|
24
29
|
}: FormPageProps<T>) {
|
25
30
|
const formOptions = useMemo(() => getFormFields(model), [model]);
|
@@ -29,6 +34,8 @@ export function FormPage<T extends AnyClass>({
|
|
29
34
|
onSubmit={onSubmit}
|
30
35
|
formOptions={formOptions}
|
31
36
|
redirectBackOnSuccess={redirectBackOnSuccess}
|
37
|
+
onSelectPreloader={onSelectPreloader}
|
38
|
+
type={type}
|
32
39
|
/>
|
33
40
|
);
|
34
41
|
}
|
@@ -15,6 +15,7 @@ export interface InputOptions {
|
|
15
15
|
placeholder?: string;
|
16
16
|
cancelPasswordValidationOnEdit?: boolean;
|
17
17
|
options?: { value: string; label: string }[];
|
18
|
+
optionsPreload?: boolean;
|
18
19
|
nestedFields?: InputOptions[];
|
19
20
|
}
|
20
21
|
|
@@ -31,7 +32,8 @@ export function Input(options?: InputOptions): PropertyDecorator {
|
|
31
32
|
}
|
32
33
|
|
33
34
|
export function getInputFields<T extends AnyClass>(entityClass: T): InputOptions[] {
|
34
|
-
|
35
|
+
//TODO2: ANY IS NOT A GOOD SOLUTION, WE NEED TO FIND A BETTER WAY TO DO THIS
|
36
|
+
const prototype = (entityClass as any).prototype;
|
35
37
|
const inputFields: string[] = Reflect.getMetadata(INPUT_KEY, prototype) || [];
|
36
38
|
return inputFields.map(field => {
|
37
39
|
const fields = Reflect.getMetadata(`${INPUT_KEY.toString()}:${field}:options`, prototype) || {};
|
@@ -5,6 +5,7 @@ import { getFormConfiguration } from "./Form";
|
|
5
5
|
import { classValidatorResolver } from "@hookform/resolvers/class-validator";
|
6
6
|
|
7
7
|
export function getFormFields<T extends AnyClass>(entityClass: T): FormOptions {
|
8
|
+
console.log(entityClass);
|
8
9
|
return {
|
9
10
|
resolver: classValidatorResolver(entityClass as any),
|
10
11
|
form: getFormConfiguration(entityClass),
|
@@ -7,18 +7,18 @@ export interface ListHeaderOptions {
|
|
7
7
|
create?: { path: string; label: string };
|
8
8
|
}
|
9
9
|
|
10
|
-
export interface
|
10
|
+
export interface ListCellOptions<T> {
|
11
11
|
details?: { path: string; label: string };
|
12
12
|
edit?: { path: string; label: string };
|
13
|
-
delete?: {
|
13
|
+
delete?: { label: string };
|
14
14
|
}
|
15
15
|
|
16
|
-
export interface ListOptions {
|
16
|
+
export interface ListOptions<T> {
|
17
17
|
headers?: ListHeaderOptions;
|
18
|
-
|
18
|
+
cells?: ((item: T) => ListCellOptions<T>) | ListCellOptions<T>;
|
19
19
|
}
|
20
20
|
|
21
|
-
export function List(options?: ListOptions): ClassDecorator {
|
21
|
+
export function List<T>(options?: ListOptions<T> | ((item: T) => ListOptions<T>)): ClassDecorator {
|
22
22
|
return (target: Function) => {
|
23
23
|
if (options) {
|
24
24
|
Reflect.defineMetadata(LIST_KEY, options, target);
|
@@ -26,6 +26,7 @@ export function List(options?: ListOptions): ClassDecorator {
|
|
26
26
|
};
|
27
27
|
}
|
28
28
|
|
29
|
-
export function getClassListData(entityClass:
|
30
|
-
|
29
|
+
export function getClassListData<T>(entityClass: T): ListOptions<T> | undefined {
|
30
|
+
//TODO: try to remove any
|
31
|
+
return Reflect.getMetadata(LIST_KEY, entityClass as any);
|
31
32
|
}
|
@@ -1,7 +1,7 @@
|
|
1
|
-
import { ListOptions } from
|
2
|
-
import { CellOptions } from
|
1
|
+
import { ListOptions } from './List';
|
2
|
+
import { CellOptions } from './Cell';
|
3
3
|
|
4
|
-
export interface ListData {
|
5
|
-
|
6
|
-
|
4
|
+
export interface ListData<T> {
|
5
|
+
list?: ListOptions<T>;
|
6
|
+
cells: CellOptions[];
|
7
7
|
}
|
@@ -1,10 +1,10 @@
|
|
1
|
-
import { getClassListData } from
|
2
|
-
import { getCellFields } from
|
3
|
-
import { ListData } from
|
1
|
+
import { getClassListData } from './List';
|
2
|
+
import { getCellFields } from './GetCellFields';
|
3
|
+
import { ListData } from './ListData';
|
4
4
|
|
5
|
-
export function getListFields<T>(entityClass: T): ListData {
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
export function getListFields<T>(entityClass: T): ListData<T> {
|
6
|
+
return {
|
7
|
+
list: getClassListData(entityClass),
|
8
|
+
cells: getCellFields(entityClass),
|
9
|
+
};
|
10
10
|
}
|
package/src/styles/form.scss
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
.form-wrapper {
|
2
|
-
max-width: 400px;
|
3
2
|
padding: 20px;
|
4
|
-
border-radius: 10px;
|
5
|
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
6
3
|
background-color: #1a1a1a;
|
7
4
|
color: #f2f2f2;
|
5
|
+
width: 100%;
|
8
6
|
|
9
7
|
form {
|
10
8
|
display: flex;
|
@@ -59,7 +57,7 @@
|
|
59
57
|
|
60
58
|
select {
|
61
59
|
appearance: none;
|
62
|
-
background-image: url(
|
60
|
+
background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23FFFFFF%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E');
|
63
61
|
background-repeat: no-repeat;
|
64
62
|
background-position: right 0.7rem top 50%;
|
65
63
|
background-size: 0.65rem auto;
|
package/src/styles/index.scss
CHANGED
@@ -1,15 +1,15 @@
|
|
1
|
-
@
|
2
|
-
|
3
|
-
@
|
4
|
-
@
|
5
|
-
@
|
6
|
-
@
|
7
|
-
@
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@
|
11
|
-
@
|
12
|
-
@
|
1
|
+
@import './layout';
|
2
|
+
@import './sidebar';
|
3
|
+
@import './form';
|
4
|
+
@import './list';
|
5
|
+
@import './login';
|
6
|
+
@import './error-boundary';
|
7
|
+
@import './image-uploader';
|
8
|
+
@import './counter';
|
9
|
+
@import './loading-screen';
|
10
|
+
@import './pagination';
|
11
|
+
@import './filter-popup';
|
12
|
+
@import './uploader';
|
13
13
|
|
14
14
|
.layout {
|
15
15
|
display: flex;
|
package/src/styles/list.scss
CHANGED
@@ -0,0 +1,86 @@
|
|
1
|
+
.uploader {
|
2
|
+
&-container {
|
3
|
+
padding: 1.5rem;
|
4
|
+
border: 2px dashed var(--border-color, #e0e0e0);
|
5
|
+
border-radius: 8px;
|
6
|
+
background-color: var(--bg-color, #fafafa);
|
7
|
+
transition: all 0.3s ease;
|
8
|
+
|
9
|
+
&:hover {
|
10
|
+
border-color: var(--primary-color, #2196f3);
|
11
|
+
background-color: var(--bg-hover-color, #f5f5f5);
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
&-button {
|
16
|
+
background-color: var(--primary-color, #2196f3);
|
17
|
+
color: var(--button-text-color, white);
|
18
|
+
padding: 0.75rem 1.5rem;
|
19
|
+
border: none;
|
20
|
+
border-radius: 4px;
|
21
|
+
font-weight: 500;
|
22
|
+
cursor: pointer;
|
23
|
+
transition: background-color 0.3s ease;
|
24
|
+
|
25
|
+
&:hover {
|
26
|
+
background-color: var(--primary-hover-color, #1976d2);
|
27
|
+
}
|
28
|
+
|
29
|
+
&:active {
|
30
|
+
background-color: var(--primary-active-color, #1565c0);
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
&-files {
|
35
|
+
margin-top: 1.5rem;
|
36
|
+
display: grid;
|
37
|
+
gap: 1rem;
|
38
|
+
}
|
39
|
+
|
40
|
+
&-file {
|
41
|
+
background-color: var(--file-bg-color, white);
|
42
|
+
padding: 1rem;
|
43
|
+
border-radius: 6px;
|
44
|
+
box-shadow: 0 2px 4px var(--shadow-color, rgba(0, 0, 0, 0.05));
|
45
|
+
display: grid;
|
46
|
+
gap: 0.5rem;
|
47
|
+
|
48
|
+
p {
|
49
|
+
margin: 0;
|
50
|
+
color: var(--text-color, #424242);
|
51
|
+
font-size: 0.875rem;
|
52
|
+
|
53
|
+
&:first-child {
|
54
|
+
color: var(--primary-color, #1976d2);
|
55
|
+
font-weight: 500;
|
56
|
+
}
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
:root {
|
62
|
+
--border-color: #424242;
|
63
|
+
--bg-color: #1e1e1e;
|
64
|
+
--bg-hover-color: #2d2d2d;
|
65
|
+
--primary-color: #64b5f6;
|
66
|
+
--primary-hover-color: #42a5f5;
|
67
|
+
--primary-active-color: #2196f3;
|
68
|
+
--button-text-color: #ffffff;
|
69
|
+
--file-bg-color: #2d2d2d;
|
70
|
+
--shadow-color: rgba(0, 0, 0, 0.2);
|
71
|
+
--text-color: #e0e0e0;
|
72
|
+
}
|
73
|
+
|
74
|
+
// Dark theme
|
75
|
+
[data-theme='dark'] {
|
76
|
+
--border-color: #424242;
|
77
|
+
--bg-color: #1e1e1e;
|
78
|
+
--bg-hover-color: #2d2d2d;
|
79
|
+
--primary-color: #64b5f6;
|
80
|
+
--primary-hover-color: #42a5f5;
|
81
|
+
--primary-active-color: #2196f3;
|
82
|
+
--button-text-color: #ffffff;
|
83
|
+
--file-bg-color: #2d2d2d;
|
84
|
+
--shadow-color: rgba(0, 0, 0, 0.2);
|
85
|
+
--text-color: #e0e0e0;
|
86
|
+
}
|
package/src/types/AnyClass.ts
CHANGED