proje-react-panel 1.0.15 → 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.
Files changed (62) hide show
  1. package/.vscode/launch.json +10 -0
  2. package/dist/components/components/FormField.d.ts +6 -1
  3. package/dist/components/components/InnerForm.d.ts +9 -4
  4. package/dist/components/components/Uploader.d.ts +8 -0
  5. package/dist/components/components/index.d.ts +1 -1
  6. package/dist/components/components/list/Datagrid.d.ts +9 -0
  7. package/dist/components/components/list/EmptyList.d.ts +2 -0
  8. package/dist/components/components/list/FilterPopup.d.ts +11 -0
  9. package/dist/components/components/list/ListPage.d.ts +20 -0
  10. package/dist/components/components/list/Pagination.d.ts +11 -0
  11. package/dist/components/components/list/index.d.ts +0 -0
  12. package/dist/components/list/Datagrid.d.ts +8 -4
  13. package/dist/components/list/EmptyList.d.ts +2 -0
  14. package/dist/components/list/FilterPopup.d.ts +10 -0
  15. package/dist/components/pages/FormPage.d.ts +10 -3
  16. package/dist/components/pages/ListPage.d.ts +2 -1
  17. package/dist/decorators/form/Input.d.ts +8 -3
  18. package/dist/decorators/list/Cell.d.ts +14 -2
  19. package/dist/decorators/list/List.d.ts +26 -4
  20. package/dist/decorators/list/ListData.d.ts +4 -4
  21. package/dist/decorators/list/getListFields.d.ts +2 -2
  22. package/dist/index.cjs.js +1 -1
  23. package/dist/index.d.ts +4 -3
  24. package/dist/index.esm.js +1 -1
  25. package/dist/types/AnyClass.d.ts +1 -1
  26. package/dist/types/ScreenCreatorData.d.ts +5 -5
  27. package/package.json +9 -3
  28. package/src/assets/icons/svg/create.svg +9 -0
  29. package/src/assets/icons/svg/filter.svg +3 -0
  30. package/src/assets/icons/svg/pencil.svg +8 -0
  31. package/src/assets/icons/svg/search.svg +8 -0
  32. package/src/assets/icons/svg/trash.svg +8 -0
  33. package/src/components/components/FormField.tsx +52 -9
  34. package/src/components/components/InnerForm.tsx +53 -15
  35. package/src/components/components/Uploader.tsx +66 -0
  36. package/src/components/components/index.ts +2 -2
  37. package/src/components/components/list/Datagrid.tsx +135 -0
  38. package/src/components/components/list/EmptyList.tsx +26 -0
  39. package/src/components/components/list/FilterPopup.tsx +202 -0
  40. package/src/components/components/list/ListPage.tsx +176 -0
  41. package/src/components/pages/FormPage.tsx +12 -3
  42. package/src/decorators/form/Input.ts +7 -4
  43. package/src/decorators/form/getFormFields.ts +1 -0
  44. package/src/decorators/list/Cell.ts +24 -14
  45. package/src/decorators/list/List.ts +26 -11
  46. package/src/decorators/list/ListData.ts +5 -5
  47. package/src/decorators/list/getListFields.ts +8 -8
  48. package/src/index.ts +8 -3
  49. package/src/styles/filter-popup.scss +134 -0
  50. package/src/styles/form.scss +2 -4
  51. package/src/styles/index.scss +18 -22
  52. package/src/styles/list.scss +151 -8
  53. package/src/styles/uploader.scss +86 -0
  54. package/src/types/AnyClass.ts +3 -1
  55. package/src/types/ScreenCreatorData.ts +12 -12
  56. package/src/types/svg.d.ts +5 -0
  57. package/src/components/components/ImageUploader.tsx +0 -301
  58. package/src/components/list/Datagrid.tsx +0 -101
  59. package/src/components/pages/ListPage.tsx +0 -85
  60. /package/src/components/{list → components/list}/Pagination.tsx +0 -0
  61. /package/src/components/{list → components/list}/index.ts +0 -0
  62. /package/src/styles/{_scrollbar.scss → utils/scrollbar.scss} +0 -0
@@ -0,0 +1,176 @@
1
+ import React, { useMemo, useCallback, useEffect, useState } from 'react';
2
+ import { Link, useParams, useNavigate } from 'react-router';
3
+ import { Datagrid } from './Datagrid';
4
+ import { ErrorComponent } from '../ErrorComponent';
5
+ import { LoadingScreen } from '../LoadingScreen';
6
+ import { AnyClass } from '../../../types/AnyClass';
7
+ import { getListFields } from '../../../decorators/list/getListFields';
8
+ import { Pagination } from './Pagination';
9
+ import { ListData } from '../../../decorators/list/ListData';
10
+ import CreateIcon from '../../../assets/icons/svg/create.svg';
11
+ import FilterIcon from '../../../assets/icons/svg/filter.svg';
12
+ import { FilterPopup } from './FilterPopup';
13
+
14
+ export interface GetDataParams {
15
+ page?: number;
16
+ limit?: number;
17
+ filters?: Record<string, any>;
18
+ }
19
+
20
+ export interface PaginatedResponse<T> {
21
+ data: T[];
22
+ total: number;
23
+ page: number;
24
+ limit: number;
25
+ }
26
+
27
+ export type GetDataForList<T> = (params: GetDataParams) => Promise<PaginatedResponse<T>>;
28
+
29
+ function ListHeader<T extends AnyClass>({
30
+ listData,
31
+ filtered,
32
+ onFilterClick,
33
+ customHeader,
34
+ }: {
35
+ listData: ListData<T>;
36
+ filtered: boolean;
37
+ onFilterClick: () => void;
38
+ customHeader?: React.ReactNode;
39
+ }) {
40
+ const fields = useMemo(() => listData.cells.filter(cell => !!cell.filter), [listData.cells]);
41
+
42
+ const header = listData.list?.headers;
43
+ return (
44
+ <div className="list-header">
45
+ <div className="header-title">{header?.title || 'List'}</div>
46
+ {customHeader && <div className="header-custom">{customHeader}</div>}
47
+ <div className="header-actions">
48
+ {!!fields.length && (
49
+ <button onClick={onFilterClick} className="filter-button">
50
+ <FilterIcon className={`icon icon-filter ${filtered ? 'active' : ''}`} />
51
+ Filter
52
+ </button>
53
+ )}
54
+ {header?.create && (
55
+ <Link to={header.create.path} className="create-button">
56
+ <CreateIcon className="icon icon-create" />
57
+ {header.create.label}
58
+ </Link>
59
+ )}
60
+ </div>
61
+ </div>
62
+ );
63
+ }
64
+
65
+ export function ListPage<T extends AnyClass>({
66
+ model,
67
+ getData,
68
+ onRemoveItem,
69
+ customHeader,
70
+ }: {
71
+ model: any;
72
+ getData: GetDataForList<T>;
73
+ customHeader?: React.ReactNode;
74
+ onRemoveItem?: (item: T) => Promise<void>;
75
+ }) {
76
+ const [loading, setLoading] = useState(true);
77
+ const [pagination, setPagination] = useState({ total: 0, page: 0, limit: 0 });
78
+ const [data, setData] = useState<any>(null);
79
+ const [error, setError] = useState<unknown>(null);
80
+ const [isFilterOpen, setIsFilterOpen] = useState(false);
81
+ const [activeFilters, setActiveFilters] = useState<Record<string, string>>();
82
+ const listData = useMemo(() => getListFields(model), [model]);
83
+ const params = useParams();
84
+ const navigate = useNavigate();
85
+
86
+ const fetchData = useCallback(
87
+ async (page: number, filters?: Record<string, string>) => {
88
+ setLoading(true);
89
+ try {
90
+ const result = await getData({ page, filters: filters ?? activeFilters ?? {} });
91
+ setData(result.data);
92
+ setPagination({
93
+ total: result.total,
94
+ page: result.page,
95
+ limit: result.limit,
96
+ });
97
+ } catch (e) {
98
+ setError(e);
99
+ console.error(e);
100
+ } finally {
101
+ setLoading(false);
102
+ }
103
+ },
104
+ [getData, activeFilters]
105
+ );
106
+
107
+ useEffect(() => {
108
+ const searchParams = new URLSearchParams(location.search);
109
+ const filtersFromUrl: Record<string, string> = {};
110
+ searchParams.forEach((value, key) => {
111
+ filtersFromUrl[key] = value;
112
+ });
113
+ setActiveFilters(filtersFromUrl);
114
+ }, [location.search]);
115
+
116
+ useEffect(() => {
117
+ if (activeFilters) {
118
+ fetchData(parseInt(params.page as string) || 1, activeFilters);
119
+ }
120
+ }, [fetchData, params.page, activeFilters]);
121
+
122
+ const handleFilterApply = (filters: Record<string, any>) => {
123
+ setActiveFilters(filters);
124
+
125
+ // Convert filters to URLSearchParams
126
+ const searchParams = new URLSearchParams();
127
+ Object.entries(filters).forEach(([key, value]) => {
128
+ if (value !== undefined && value !== null && value !== '') {
129
+ searchParams.append(key, String(value));
130
+ }
131
+ });
132
+ const queryString = searchParams.toString();
133
+ const newUrl = `${location.pathname}${queryString ? `?${queryString}` : ''}`;
134
+ navigate(newUrl);
135
+ fetchData(1, filters); // Reset to first page when filters change
136
+ };
137
+
138
+ if (loading) return <LoadingScreen />;
139
+ if (error) return <ErrorComponent error={error} />;
140
+
141
+ return (
142
+ <div className="list">
143
+ <ListHeader
144
+ listData={listData}
145
+ filtered={!!(activeFilters && !!Object.keys(activeFilters).length)}
146
+ onFilterClick={() => setIsFilterOpen(true)}
147
+ customHeader={customHeader}
148
+ />
149
+ <Datagrid
150
+ listData={listData}
151
+ data={data}
152
+ onRemoveItem={async (item: T) => {
153
+ if (onRemoveItem) {
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
+ }
161
+ }
162
+ }}
163
+ />
164
+ <div className="list-footer">
165
+ <Pagination pagination={pagination} onPageChange={fetchData} />
166
+ </div>
167
+ <FilterPopup
168
+ isOpen={isFilterOpen}
169
+ activeFilters={activeFilters}
170
+ onClose={() => setIsFilterOpen(false)}
171
+ onApplyFilters={handleFilterApply}
172
+ listData={listData}
173
+ />
174
+ </div>
175
+ );
176
+ }
@@ -2,15 +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
- export type GetDetailsDataFN<T> = (param: string) => Promise<T>;
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 }[]>;
16
+ redirectBackOnSuccess?: boolean;
17
+ type?: 'json' | 'formData';
14
18
  }
15
19
 
16
20
  export function FormPage<T extends AnyClass>({
@@ -18,15 +22,20 @@ export function FormPage<T extends AnyClass>({
18
22
  getDetailsData,
19
23
  onSubmit,
20
24
  redirect,
25
+ onSelectPreloader,
26
+ redirectBackOnSuccess = true,
27
+ type = 'json',
21
28
  ...rest
22
29
  }: FormPageProps<T>) {
23
30
  const formOptions = useMemo(() => getFormFields(model), [model]);
24
31
  return (
25
32
  <InnerForm
26
33
  getDetailsData={getDetailsData}
27
- redirect={redirect}
28
34
  onSubmit={onSubmit}
29
35
  formOptions={formOptions}
36
+ redirectBackOnSuccess={redirectBackOnSuccess}
37
+ onSelectPreloader={onSelectPreloader}
38
+ type={type}
30
39
  />
31
40
  );
32
41
  }
@@ -8,13 +8,15 @@ const isFieldSensitive = (fieldName: string): boolean => {
8
8
  };
9
9
 
10
10
  export interface InputOptions {
11
+ type?: 'input' | 'select' | 'textarea' | 'file-upload' | 'checkbox' | 'hidden' | 'nested';
12
+ inputType?: 'text' | 'email' | 'tel' | 'password' | 'number' | 'date';
11
13
  name?: string;
12
14
  label?: string;
13
15
  placeholder?: string;
14
- inputType?: 'text' | 'email' | 'tel' | 'password' | 'number' | 'date';
15
- type?: 'input' | 'select' | 'textarea' | 'file-upload' | 'checkbox' | 'hidden';
16
- selectOptions?: string[]; //TODO: label/value
17
16
  cancelPasswordValidationOnEdit?: boolean;
17
+ options?: { value: string; label: string }[];
18
+ optionsPreload?: boolean;
19
+ nestedFields?: InputOptions[];
18
20
  }
19
21
 
20
22
  export function Input(options?: InputOptions): PropertyDecorator {
@@ -30,7 +32,8 @@ export function Input(options?: InputOptions): PropertyDecorator {
30
32
  }
31
33
 
32
34
  export function getInputFields<T extends AnyClass>(entityClass: T): InputOptions[] {
33
- const prototype = entityClass.prototype;
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;
34
37
  const inputFields: string[] = Reflect.getMetadata(INPUT_KEY, prototype) || [];
35
38
  return inputFields.map(field => {
36
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),
@@ -1,22 +1,32 @@
1
- import "reflect-metadata";
1
+ import 'reflect-metadata';
2
2
 
3
- export const CELL_KEY = Symbol("cell");
3
+ export const CELL_KEY = Symbol('cell');
4
+
5
+ interface Filter {
6
+ type: 'string' | 'number' | 'date' | 'static-select';
7
+ }
8
+
9
+ export interface StaticSelectFilter extends Filter {
10
+ type: 'static-select';
11
+ options: { value: string; label: string }[];
12
+ }
4
13
 
5
14
  export interface CellOptions {
6
- name?: string;
7
- title?: string;
8
- type?: "string" | "date" | "image";
9
- placeHolder?: string;
15
+ name?: string;
16
+ title?: string;
17
+ type?: 'string' | 'date' | 'image' | 'number';
18
+ placeHolder?: string;
19
+ filter?: Filter | StaticSelectFilter;
10
20
  }
11
21
 
12
22
  export function Cell(options?: CellOptions): PropertyDecorator {
13
- return (target, propertyKey) => {
14
- const existingCells: string[] = Reflect.getMetadata(CELL_KEY, target) || [];
15
- Reflect.defineMetadata(CELL_KEY, [...existingCells, propertyKey.toString()], target);
23
+ return (target, propertyKey) => {
24
+ const existingCells: string[] = Reflect.getMetadata(CELL_KEY, target) || [];
25
+ Reflect.defineMetadata(CELL_KEY, [...existingCells, propertyKey.toString()], target);
16
26
 
17
- if (options) {
18
- const keyString = `${CELL_KEY.toString()}:${propertyKey.toString()}:options`;
19
- Reflect.defineMetadata(keyString, options, target);
20
- }
21
- };
27
+ if (options) {
28
+ const keyString = `${CELL_KEY.toString()}:${propertyKey.toString()}:options`;
29
+ Reflect.defineMetadata(keyString, options, target);
30
+ }
31
+ };
22
32
  }
@@ -1,17 +1,32 @@
1
- import "reflect-metadata";
1
+ import 'reflect-metadata';
2
2
 
3
- const LIST_KEY = "List";
3
+ const LIST_KEY = 'List';
4
4
 
5
- export interface ListOptions {}
5
+ export interface ListHeaderOptions {
6
+ title?: string;
7
+ create?: { path: string; label: string };
8
+ }
9
+
10
+ export interface ListCellOptions<T> {
11
+ details?: { path: string; label: string };
12
+ edit?: { path: string; label: string };
13
+ delete?: { label: string };
14
+ }
15
+
16
+ export interface ListOptions<T> {
17
+ headers?: ListHeaderOptions;
18
+ cells?: ((item: T) => ListCellOptions<T>) | ListCellOptions<T>;
19
+ }
6
20
 
7
- export function List(options?: ListOptions): ClassDecorator {
8
- return (target: Function) => {
9
- if (options) {
10
- Reflect.defineMetadata(LIST_KEY, options, target);
11
- }
12
- };
21
+ export function List<T>(options?: ListOptions<T> | ((item: T) => ListOptions<T>)): ClassDecorator {
22
+ return (target: Function) => {
23
+ if (options) {
24
+ Reflect.defineMetadata(LIST_KEY, options, target);
25
+ }
26
+ };
13
27
  }
14
28
 
15
- export function getClassListData(entityClass: any): ListOptions | undefined {
16
- return Reflect.getMetadata(LIST_KEY, entityClass);
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);
17
32
  }
@@ -1,7 +1,7 @@
1
- import { ListOptions } from "./List";
2
- import { CellOptions } from "./Cell";
1
+ import { ListOptions } from './List';
2
+ import { CellOptions } from './Cell';
3
3
 
4
- export interface ListData {
5
- list?: ListOptions;
6
- cells: CellOptions[];
4
+ export interface ListData<T> {
5
+ list?: ListOptions<T>;
6
+ cells: CellOptions[];
7
7
  }
@@ -1,10 +1,10 @@
1
- import { getClassListData } from "./List";
2
- import { getCellFields } from "./GetCellFields";
3
- import { ListData } from "./ListData";
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
- return {
7
- list: getClassListData(entityClass),
8
- cells: getCellFields(entityClass),
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/index.ts CHANGED
@@ -1,12 +1,15 @@
1
1
  export type { OnSubmitFN, GetDetailsDataFN } from './components/pages/FormPage';
2
- export type { GetDataForList } from './components/pages/ListPage';
3
- export type { PaginatedResponse, PaginationParams } from './components/pages/ListPage';
4
2
  export type { InitPanelOptions } from './types/initPanelOptions';
5
3
  export type { ScreenCreatorData } from './types/ScreenCreatorData';
6
4
  export type { OnLogin } from './components/pages/Login';
7
5
  export type { AnyClass } from './types/AnyClass';
6
+ export type {
7
+ GetDataForList,
8
+ PaginatedResponse,
9
+ GetDataParams,
10
+ } from './components/components/list/ListPage';
8
11
 
9
- export { ListPage } from './components/pages/ListPage';
12
+ export { ListPage } from './components/components/list/ListPage';
10
13
  export { FormPage } from './components/pages/FormPage';
11
14
  export type { FormPageProps } from './components/pages/FormPage';
12
15
  export { Login } from './components/pages/Login';
@@ -21,3 +24,5 @@ export { Crud } from './decorators/Crud';
21
24
  export { Cell } from './decorators/list/Cell';
22
25
  export { Input } from './decorators/form/Input';
23
26
  // Export page components
27
+ export { getFormFields } from './decorators/form/getFormFields';
28
+ export { getInputFields } from './decorators/form/Input';
@@ -0,0 +1,134 @@
1
+ .filter-popup {
2
+ position: absolute;
3
+ top: 60px; //TODO: make this variable $list-header-height
4
+ //TODO: make this variable
5
+ left: 260px;
6
+ right: 10px;
7
+ background: #1e1e1e;
8
+ padding: 20px;
9
+ border-radius: 8px;
10
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
11
+ border: 1px solid #333;
12
+ z-index: 10;
13
+ &-header {
14
+ display: flex;
15
+ justify-content: space-between;
16
+ align-items: center;
17
+ margin-bottom: 20px;
18
+
19
+ h3 {
20
+ margin: 0;
21
+ font-size: 1.2rem;
22
+ color: #ffffff;
23
+ }
24
+ }
25
+
26
+ &-content {
27
+ max-height: 400px;
28
+ overflow-y: auto;
29
+
30
+ &::-webkit-scrollbar {
31
+ width: 8px;
32
+ }
33
+
34
+ &::-webkit-scrollbar-track {
35
+ background: #2d2d2d;
36
+ border-radius: 4px;
37
+ }
38
+
39
+ &::-webkit-scrollbar-thumb {
40
+ background: #444;
41
+ border-radius: 4px;
42
+
43
+ &:hover {
44
+ background: #555;
45
+ }
46
+ }
47
+ }
48
+
49
+ &-footer {
50
+ display: flex;
51
+ justify-content: flex-end;
52
+ gap: 10px;
53
+ margin-top: 20px;
54
+ }
55
+ }
56
+
57
+ .close-button {
58
+ background: none;
59
+ border: none;
60
+ font-size: 1.5rem;
61
+ cursor: pointer;
62
+ padding: 0;
63
+ color: #999;
64
+ transition: color 0.2s ease;
65
+
66
+ &:hover {
67
+ color: #ffffff;
68
+ }
69
+ }
70
+
71
+ .filter-field {
72
+ margin-bottom: 15px;
73
+
74
+ label {
75
+ display: block;
76
+ margin-bottom: 5px;
77
+ font-size: 0.9rem;
78
+ color: #bbb;
79
+ }
80
+
81
+ input {
82
+ width: 100%;
83
+ padding: 10px;
84
+ border: 1px solid #444;
85
+ border-radius: 6px;
86
+ font-size: 0.9rem;
87
+ background: #2d2d2d;
88
+ color: #ffffff;
89
+ transition: all 0.2s ease;
90
+
91
+ &:focus {
92
+ border-color: #0066cc;
93
+ outline: none;
94
+ box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.2);
95
+ }
96
+
97
+ &::placeholder {
98
+ color: #666;
99
+ }
100
+ }
101
+ }
102
+
103
+ .cancel-button,
104
+ .apply-button {
105
+ padding: 10px 20px;
106
+ border-radius: 6px;
107
+ cursor: pointer;
108
+ font-size: 0.9rem;
109
+ transition: all 0.2s ease;
110
+ }
111
+
112
+ .cancel-button {
113
+ background: #2d2d2d;
114
+ border: 1px solid #444;
115
+ color: #bbb;
116
+
117
+ &:hover {
118
+ background: #333;
119
+ border-color: #555;
120
+ color: #fff;
121
+ }
122
+ }
123
+
124
+ .apply-button {
125
+ background: #0066cc;
126
+ border: 1px solid #0056b3;
127
+ color: white;
128
+
129
+ &:hover {
130
+ background: #0056b3;
131
+ transform: translateY(-1px);
132
+ box-shadow: 0 2px 8px rgba(0, 102, 204, 0.3);
133
+ }
134
+ }
@@ -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("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");
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;
@@ -1,13 +1,15 @@
1
- @forward './sidebar';
2
- @forward './form';
3
- @forward './layout';
4
- @forward './list';
5
- @forward './login';
6
- @forward './error-boundary';
7
- @forward './image-uploader';
8
- @forward './counter';
9
- @forward './loading-screen';
10
- @forward './pagination';
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';
11
13
 
12
14
  .layout {
13
15
  display: flex;
@@ -31,16 +33,10 @@
31
33
  *::after {
32
34
  box-sizing: border-box;
33
35
  }
34
-
35
- // Imported styles will be scoped under .layout
36
- @import './sidebar';
37
- @import './form';
38
- @import './list';
39
- @import './login';
40
- @import './error-boundary';
41
- @import './image-uploader';
42
- @import './counter';
43
- @import './loading-screen';
44
- @import './pagination';
45
- @import './scrollbar';
36
+ }
37
+ .icon {
38
+ width: 14px;
39
+ height: 14px;
40
+ stroke: currentColor;
41
+ fill: currentColor;
46
42
  }