proje-react-panel 1.6.0 → 1.7.0
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/.cursor/rules.md +11 -1
- package/AUTH_LAYOUT_EXAMPLE.md +343 -0
- package/AUTH_LAYOUT_GUIDE.md +819 -0
- package/COLOR_SYSTEM_GUIDE.md +296 -0
- package/DASHBOARD_GUIDE.md +531 -0
- package/IMPLEMENTATION_GUIDE.md +899 -0
- package/README.md +18 -1
- package/dist/api/ApiConfig.d.ts +11 -0
- package/dist/api/AuthApi.d.ts +2 -5
- package/dist/api/CrudApi.d.ts +11 -12
- package/dist/components/DashboardContainer.d.ts +7 -0
- package/dist/components/DashboardGrid.d.ts +9 -0
- package/dist/components/DashboardItem.d.ts +10 -0
- package/dist/components/ThemeSwitcher.d.ts +7 -0
- package/dist/components/dashboard/Dashboard.d.ts +7 -0
- package/dist/components/dashboard/DashboardGrid.d.ts +7 -0
- package/dist/components/dashboard/DashboardItem.d.ts +6 -0
- package/dist/components/dashboard/index.d.ts +3 -0
- package/dist/decorators/auth/DefaultLoginForm.d.ts +4 -0
- package/dist/index.cjs.js +15 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.esm.js +15 -1
- package/dist/store/themeStore.d.ts +23 -0
- package/dist/types/Login.d.ts +8 -0
- package/package.json +3 -1
- package/src/api/ApiConfig.ts +63 -0
- package/src/api/AuthApi.ts +8 -0
- package/src/api/CrudApi.ts +96 -60
- package/src/components/dashboard/Dashboard.tsx +11 -0
- package/src/components/dashboard/DashboardGrid.tsx +14 -0
- package/src/components/dashboard/DashboardItem.tsx +9 -0
- package/src/components/dashboard/index.ts +3 -0
- package/src/decorators/auth/DefaultLoginForm.ts +32 -0
- package/src/index.ts +26 -0
- package/src/styles/base/_variables.scss +45 -0
- package/src/styles/components/button.scss +3 -3
- package/src/styles/components/checkbox.scss +6 -6
- package/src/styles/components/form-header.scss +21 -19
- package/src/styles/components/uploader.scss +15 -37
- package/src/styles/counter.scss +25 -33
- package/src/styles/dashboard.scss +9 -0
- package/src/styles/details.scss +6 -15
- package/src/styles/error-boundary.scss +75 -74
- package/src/styles/filter-popup.scss +29 -27
- package/src/styles/form.scss +16 -15
- package/src/styles/index.scss +8 -4
- package/src/styles/layout.scss +9 -8
- package/src/styles/list.scss +29 -27
- package/src/styles/loading-screen.scss +4 -4
- package/src/styles/login.scss +3 -3
- package/src/styles/pagination.scss +13 -13
- package/src/styles/sidebar.scss +24 -22
- package/src/styles/utils/scrollbar.scss +4 -3
- package/src/types/Login.ts +9 -0
- package/src/utils/logout.ts +2 -0
- package/dist/components/components/Checkbox.d.ts +0 -7
- package/dist/components/components/Counter.d.ts +0 -9
- package/dist/components/components/ErrorBoundary.d.ts +0 -16
- package/dist/components/components/ErrorComponent.d.ts +0 -4
- package/dist/components/components/FormField.d.ts +0 -17
- package/dist/components/components/ImageUploader.d.ts +0 -15
- package/dist/components/components/InnerForm.d.ts +0 -17
- package/dist/components/components/Label.d.ts +0 -9
- package/dist/components/components/LoadingScreen.d.ts +0 -2
- package/dist/components/components/Uploader.d.ts +0 -8
- package/dist/components/components/index.d.ts +0 -8
- package/dist/components/components/list/Datagrid.d.ts +0 -9
- package/dist/components/components/list/EmptyList.d.ts +0 -2
- package/dist/components/components/list/FilterPopup.d.ts +0 -11
- package/dist/components/components/list/ListPage.d.ts +0 -20
- package/dist/components/components/list/Pagination.d.ts +0 -11
- package/dist/components/components/list/index.d.ts +0 -0
- package/dist/components/pages/ControllerDetails.d.ts +0 -5
- package/dist/components/pages/FormPage.d.ts +0 -18
- package/dist/components/pages/ListPage.d.ts +0 -18
- package/dist/components/pages/Login.d.ts +0 -13
- package/dist/decorators/Crud.d.ts +0 -6
- package/dist/decorators/form/FormOptions.d.ts +0 -7
- package/dist/decorators/form/getFormFields.d.ts +0 -3
- package/dist/decorators/list/GetCellFields.d.ts +0 -2
- package/dist/decorators/list/ImageCell.d.ts +0 -6
- package/dist/decorators/list/ListData.d.ts +0 -6
- package/dist/decorators/list/getListFields.d.ts +0 -2
- package/dist/initPanel.d.ts +0 -2
- package/dist/types/Screen.d.ts +0 -4
- package/dist/types/ScreenCreatorData.d.ts +0 -13
- package/dist/types/getDetailsData.d.ts +0 -1
- package/dist/types/initPanelOptions.d.ts +0 -2
- package/dist/utils/createScreens.d.ts +0 -1
- package/dist/utils/getFields.d.ts +0 -3
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type ThemeMode = 'light' | 'dark' | 'automatic';
|
|
2
|
+
interface ThemeState {
|
|
3
|
+
theme: ThemeMode;
|
|
4
|
+
resolvedTheme: 'light' | 'dark';
|
|
5
|
+
setTheme: (theme: ThemeMode) => void;
|
|
6
|
+
initializeTheme: () => void;
|
|
7
|
+
}
|
|
8
|
+
export declare const useThemeStore: import("zustand/traditional").UseBoundStoreWithEqualityFn<Omit<import("zustand/vanilla").StoreApi<ThemeState>, "persist"> & {
|
|
9
|
+
persist: {
|
|
10
|
+
setOptions: (options: Partial<import("zustand/middleware").PersistOptions<ThemeState, unknown>>) => void;
|
|
11
|
+
clearStorage: () => void;
|
|
12
|
+
rehydrate: () => Promise<void> | void;
|
|
13
|
+
hasHydrated: () => boolean;
|
|
14
|
+
onHydrate: (fn: (state: ThemeState) => void) => () => void;
|
|
15
|
+
onFinishHydration: (fn: (state: ThemeState) => void) => () => void;
|
|
16
|
+
getOptions: () => Partial<import("zustand/middleware").PersistOptions<ThemeState, unknown>>;
|
|
17
|
+
};
|
|
18
|
+
}>;
|
|
19
|
+
export declare const getTheme: () => ThemeMode;
|
|
20
|
+
export declare const getResolvedTheme: () => "light" | "dark";
|
|
21
|
+
export declare const setTheme: (theme: ThemeMode) => void;
|
|
22
|
+
export declare const initializeTheme: () => void;
|
|
23
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proje-react-panel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "",
|
|
6
6
|
"author": "SEFA DEMİR",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@types/react": "^19.0.10",
|
|
34
34
|
"@typescript-eslint/eslint-plugin": "^8.26.1",
|
|
35
35
|
"@typescript-eslint/parser": "^8.29.1",
|
|
36
|
+
"axios": "^1.12.2",
|
|
36
37
|
"class-transformer": "^0.5.1",
|
|
37
38
|
"class-validator": "^0.14.1",
|
|
38
39
|
"eslint": "^9.24.0",
|
|
@@ -61,6 +62,7 @@
|
|
|
61
62
|
"zustand": "^5.0.3"
|
|
62
63
|
},
|
|
63
64
|
"peerDependencies": {
|
|
65
|
+
"axios": ">=1.0.0",
|
|
64
66
|
"react": ">=19.0.0",
|
|
65
67
|
"react-hook-form": ">=7.54.2",
|
|
66
68
|
"react-router": "7.3.0",
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import axios, { AxiosInstance } from 'axios';
|
|
2
|
+
|
|
3
|
+
let axiosInstance: AxiosInstance;
|
|
4
|
+
|
|
5
|
+
export interface ApiConfig {
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function initApi(config: ApiConfig): void {
|
|
10
|
+
axiosInstance = axios.create({
|
|
11
|
+
baseURL: config.baseUrl,
|
|
12
|
+
headers: {
|
|
13
|
+
'Content-Type': 'application/json',
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
axiosInstance.interceptors.response.use(
|
|
18
|
+
response => response,
|
|
19
|
+
error => {
|
|
20
|
+
if (error.response && error.response.status === 401) {
|
|
21
|
+
setAuthLogout();
|
|
22
|
+
window.location.href = '/login';
|
|
23
|
+
}
|
|
24
|
+
return Promise.reject(error);
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function initAuthToken(): void {
|
|
30
|
+
if (!axiosInstance) {
|
|
31
|
+
throw new Error('API not initialized. Call initApi first.');
|
|
32
|
+
}
|
|
33
|
+
const token = localStorage.getItem('token');
|
|
34
|
+
if (token) {
|
|
35
|
+
axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${token}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function setAuthToken(token: string): void {
|
|
40
|
+
if (!axiosInstance) {
|
|
41
|
+
throw new Error('API not initialized. Call initApi first.');
|
|
42
|
+
}
|
|
43
|
+
axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${token}`;
|
|
44
|
+
localStorage.setItem('token', token);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function setAuthLogout(): void {
|
|
48
|
+
if (!axiosInstance) {
|
|
49
|
+
throw new Error('API not initialized. Call initApi first.');
|
|
50
|
+
}
|
|
51
|
+
axiosInstance.defaults.headers.common['Authorization'] = null;
|
|
52
|
+
localStorage.removeItem('token');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getAxiosInstance(): AxiosInstance {
|
|
56
|
+
if (!axiosInstance) {
|
|
57
|
+
throw new Error('API not initialized. Call initApi first.');
|
|
58
|
+
}
|
|
59
|
+
return axiosInstance;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Export the axios instance for external use
|
|
63
|
+
export { axiosInstance };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { LoginForm, LoginResponse } from '../types/Login';
|
|
2
|
+
import { getAxiosInstance } from './ApiConfig';
|
|
3
|
+
|
|
4
|
+
export async function login<T>(data: LoginForm | FormData): Promise<LoginResponse<T>> {
|
|
5
|
+
const axiosInstance = getAxiosInstance();
|
|
6
|
+
const response = await axiosInstance.post<LoginResponse<T>>('/auth/login', data);
|
|
7
|
+
return response.data;
|
|
8
|
+
}
|
package/src/api/CrudApi.ts
CHANGED
|
@@ -1,65 +1,101 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
1
|
+
import { GetDataForList, PaginatedResponse, GetDataParams } from '../decorators/list/List';
|
|
2
|
+
import { OnSubmitFN } from '../decorators/form/Form';
|
|
3
|
+
import { GetDetailsDataFN } from '../decorators/details/Details';
|
|
4
|
+
import { getAxiosInstance } from './ApiConfig';
|
|
5
|
+
import { AxiosError } from 'axios';
|
|
5
6
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
throw res;
|
|
7
|
+
export function getAll<T>(endpoint: string): GetDataForList<T> {
|
|
8
|
+
return async (params: GetDataParams): Promise<PaginatedResponse<T>> => {
|
|
9
|
+
const axiosInstance = getAxiosInstance();
|
|
10
|
+
const { page = 1, limit = 10 } = params;
|
|
11
|
+
const response = await axiosInstance.get<{
|
|
12
|
+
data: T[];
|
|
13
|
+
total: number;
|
|
14
|
+
}>(`/${endpoint}`, {
|
|
15
|
+
params: { page, limit, ...(params.filters ?? {}) },
|
|
16
16
|
});
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
return {
|
|
18
|
+
data: response.data.data,
|
|
19
|
+
total: response.data.total,
|
|
20
|
+
page,
|
|
21
|
+
limit,
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getOne<T>(endpoint: string, key = 'id'): GetDetailsDataFN<T> {
|
|
27
|
+
return async (params: Record<string, string>): Promise<T> => {
|
|
28
|
+
const axiosInstance = getAxiosInstance();
|
|
29
|
+
const response = await axiosInstance.get<T>(`/${endpoint}/${params[key]}`);
|
|
30
|
+
return response.data;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
26
33
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
export function create<T>(endpoint: string): OnSubmitFN<T> {
|
|
35
|
+
return async (data: T | FormData): Promise<T> => {
|
|
36
|
+
const axiosInstance = getAxiosInstance();
|
|
37
|
+
const response = await axiosInstance.post<T>(`/${endpoint}`, data);
|
|
38
|
+
return response.data;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function createFormData<T>(endpoint: string): OnSubmitFN<T> {
|
|
43
|
+
return async (data: T | FormData): Promise<T> => {
|
|
44
|
+
const axiosInstance = getAxiosInstance();
|
|
45
|
+
const response = await axiosInstance.post<T>(`/${endpoint}`, data, {
|
|
46
|
+
headers: {
|
|
47
|
+
'Content-Type': 'multipart/form-data',
|
|
48
|
+
},
|
|
41
49
|
});
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
return response.data;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function update<T>(endpoint: string, key = 'id'): OnSubmitFN<T> {
|
|
55
|
+
return async (data: T | FormData): Promise<T> => {
|
|
56
|
+
const axiosInstance = getAxiosInstance();
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
+
const id = (data as any)[key];
|
|
59
|
+
const response = await axiosInstance.put<T>(`/${endpoint}/${id}`, data);
|
|
60
|
+
return response.data;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function updateFormData<T>(endpoint: string, key = 'id'): OnSubmitFN<T> {
|
|
65
|
+
return async (data: T | FormData): Promise<T> => {
|
|
66
|
+
const axiosInstance = getAxiosInstance();
|
|
67
|
+
const id = (data as any)[key];
|
|
68
|
+
const response = await axiosInstance.put<T>(`/${endpoint}/${id}`, data, {
|
|
69
|
+
headers: {
|
|
70
|
+
'Content-Type': 'multipart/form-data',
|
|
71
|
+
},
|
|
63
72
|
});
|
|
64
|
-
|
|
65
|
-
};
|
|
73
|
+
return response.data;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function updateSimple<T>(endpoint: string): OnSubmitFN<T> {
|
|
78
|
+
return async (data: T | FormData): Promise<T> => {
|
|
79
|
+
const axiosInstance = getAxiosInstance();
|
|
80
|
+
const response = await axiosInstance.put<T>(`/${endpoint}`, data);
|
|
81
|
+
return response.data;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function remove<T>(endpoint: string, key = 'id'): (data: T) => Promise<void> {
|
|
86
|
+
return async (data: T): Promise<void> => {
|
|
87
|
+
const axiosInstance = getAxiosInstance();
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
89
|
+
const id = (data as any)[key];
|
|
90
|
+
await axiosInstance
|
|
91
|
+
.delete<T>(`/${endpoint}/${id}`)
|
|
92
|
+
.then((res: any) => res.data)
|
|
93
|
+
.catch((err: AxiosError) => {
|
|
94
|
+
const messageError = err.response?.data as { message: string };
|
|
95
|
+
if (messageError?.message) {
|
|
96
|
+
throw new Error(messageError.message);
|
|
97
|
+
}
|
|
98
|
+
throw err;
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { DashboardGrid } from './DashboardGrid';
|
|
3
|
+
|
|
4
|
+
interface DashboardProps {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
columns?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function Dashboard({ children, columns }: DashboardProps) {
|
|
10
|
+
return <DashboardGrid columns={columns}>{children}</DashboardGrid>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface DashboardGridProps {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
columns?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function DashboardGrid({ children, columns = 3 }: DashboardGridProps) {
|
|
9
|
+
return (
|
|
10
|
+
<div className="dashboard-grid" style={{ gridTemplateColumns: `repeat(${columns}, 1fr)` }}>
|
|
11
|
+
{children}
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { MinLength } from 'class-validator';
|
|
2
|
+
import { Form } from '../form/Form';
|
|
3
|
+
import { Input } from '../form/Input';
|
|
4
|
+
import { LoginForm as LoginFormInterface, LoginResponse } from '../../types/Login';
|
|
5
|
+
import { setAuthToken } from '../../api/ApiConfig';
|
|
6
|
+
import { login } from '../../utils/login';
|
|
7
|
+
import { User } from '../../types/User';
|
|
8
|
+
import { authLogin } from '../..';
|
|
9
|
+
|
|
10
|
+
@Form<LoginFormInterface, LoginResponse<User>>({
|
|
11
|
+
onSubmit: authLogin,
|
|
12
|
+
onSubmitSuccess: (data: LoginResponse<User>) => {
|
|
13
|
+
setAuthToken(data.access_token);
|
|
14
|
+
login(data.admin as User, data.access_token, () => {
|
|
15
|
+
window.location.href = '/';
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
type: 'formData',
|
|
19
|
+
})
|
|
20
|
+
export class DefaultLoginForm {
|
|
21
|
+
@MinLength(3)
|
|
22
|
+
@Input({
|
|
23
|
+
label: 'Username',
|
|
24
|
+
})
|
|
25
|
+
username: string;
|
|
26
|
+
|
|
27
|
+
@Input({
|
|
28
|
+
label: 'Password',
|
|
29
|
+
inputType: 'password',
|
|
30
|
+
})
|
|
31
|
+
password: string;
|
|
32
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ export { Panel } from './components/Panel';
|
|
|
29
29
|
|
|
30
30
|
//DASHBOARD
|
|
31
31
|
export { Counter } from './components/Counter';
|
|
32
|
+
export { Dashboard, DashboardGrid, DashboardItem } from './components/dashboard';
|
|
32
33
|
|
|
33
34
|
//LAYOUT
|
|
34
35
|
export { Layout } from './components/layout';
|
|
@@ -37,6 +38,31 @@ export { Layout } from './components/layout';
|
|
|
37
38
|
export { Login } from './components/Login';
|
|
38
39
|
export { login } from './utils/login';
|
|
39
40
|
export { logout } from './utils/logout';
|
|
41
|
+
export { DefaultLoginForm } from './decorators/auth/DefaultLoginForm';
|
|
42
|
+
|
|
43
|
+
//API
|
|
44
|
+
export {
|
|
45
|
+
initApi,
|
|
46
|
+
initAuthToken,
|
|
47
|
+
setAuthToken,
|
|
48
|
+
setAuthLogout,
|
|
49
|
+
getAxiosInstance,
|
|
50
|
+
axiosInstance,
|
|
51
|
+
} from './api/ApiConfig';
|
|
52
|
+
export {
|
|
53
|
+
getAll,
|
|
54
|
+
getOne,
|
|
55
|
+
create,
|
|
56
|
+
createFormData,
|
|
57
|
+
update,
|
|
58
|
+
updateFormData,
|
|
59
|
+
updateSimple,
|
|
60
|
+
remove,
|
|
61
|
+
} from './api/CrudApi';
|
|
62
|
+
export { login as authLogin } from './api/AuthApi';
|
|
63
|
+
|
|
64
|
+
//TYPES
|
|
65
|
+
export type { LoginForm, LoginResponse } from './types/Login';
|
|
40
66
|
|
|
41
67
|
//SERVICES
|
|
42
68
|
export { updateDetailsData } from './services/DataService';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// SCSS Color Variables - Dark Theme
|
|
2
|
+
// ⚠️ IMPORTANT: If you change any variables here, you MUST update COLOR_SYSTEM_GUIDE.md
|
|
3
|
+
$dark-void: #1a1a1a; // Darkest background
|
|
4
|
+
$dark-slate: #2b2b2b; // Dark background
|
|
5
|
+
$dark-mist: #333333; // Medium dark background
|
|
6
|
+
$dark-silver: #444444; // Lighter dark background
|
|
7
|
+
|
|
8
|
+
$dark-pearl: #ffffff; // White
|
|
9
|
+
$dark-ash: #e0e0e0; // Light gray
|
|
10
|
+
$dark-dust: #999999; // Medium gray
|
|
11
|
+
|
|
12
|
+
$dark-ocean: #007bff; // Primary blue
|
|
13
|
+
$dark-sky: #66b2ff; // Light blue (focus)
|
|
14
|
+
$dark-emerald: #4caf50; // Success green
|
|
15
|
+
$dark-ruby: #ff4d4f; // Error red
|
|
16
|
+
|
|
17
|
+
.layout {
|
|
18
|
+
// Backgrounds
|
|
19
|
+
--prp-bg-primary: #{$dark-void};
|
|
20
|
+
--prp-bg-secondary: #{$dark-slate};
|
|
21
|
+
--prp-bg-tertiary: #{$dark-mist};
|
|
22
|
+
--prp-bg-white: #{$dark-pearl};
|
|
23
|
+
--prp-bg-button-primary: #{$dark-ocean};
|
|
24
|
+
--prp-bg-button-success: #{$dark-emerald};
|
|
25
|
+
|
|
26
|
+
// Text
|
|
27
|
+
--prp-text-primary: #{$dark-pearl};
|
|
28
|
+
--prp-text-secondary: #{$dark-ash};
|
|
29
|
+
--prp-text-muted: #{$dark-dust};
|
|
30
|
+
|
|
31
|
+
// Borders
|
|
32
|
+
--prp-border-primary: #{$dark-silver};
|
|
33
|
+
--prp-border-light: #{$dark-ash};
|
|
34
|
+
|
|
35
|
+
// Colors
|
|
36
|
+
--prp-color-focus: #{$dark-sky};
|
|
37
|
+
--prp-color-success: #{$dark-emerald};
|
|
38
|
+
--prp-color-error: #{$dark-ruby};
|
|
39
|
+
|
|
40
|
+
// Shadows
|
|
41
|
+
--prp-shadow-light: rgba(0, 0, 0, 0.05);
|
|
42
|
+
--prp-shadow-medium: rgba(0, 0, 0, 0.2);
|
|
43
|
+
--prp-shadow-dark: rgba(0, 0, 0, 0.3);
|
|
44
|
+
--prp-shadow-focus: rgba(102, 178, 255, 0.2);
|
|
45
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
.panel-button {
|
|
2
2
|
padding: 0.5rem 1rem;
|
|
3
|
-
border: 1px solid
|
|
3
|
+
border: 1px solid var(--prp-border-primary);
|
|
4
4
|
border-radius: 4px;
|
|
5
|
-
background-color:
|
|
6
|
-
color:
|
|
5
|
+
background-color: var(--prp-bg-button-primary);
|
|
6
|
+
color: var(--prp-text-primary);
|
|
7
7
|
font-size: 0.875rem;
|
|
8
8
|
font-weight: 500;
|
|
9
9
|
cursor: pointer;
|
|
@@ -13,10 +13,10 @@ input.apple-switch {
|
|
|
13
13
|
outline: none;
|
|
14
14
|
width: 50px;
|
|
15
15
|
height: 30px;
|
|
16
|
-
background-color:
|
|
16
|
+
background-color: var(--prp-bg-white);
|
|
17
17
|
border: 1px solid #d9dadc;
|
|
18
18
|
border-radius: 50px;
|
|
19
|
-
box-shadow: inset -20px 0 0 0
|
|
19
|
+
box-shadow: inset -20px 0 0 0 var(--prp-bg-white);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
input.apple-switch:after {
|
|
@@ -28,15 +28,15 @@ input.apple-switch:after {
|
|
|
28
28
|
width: 26px;
|
|
29
29
|
height: 26px;
|
|
30
30
|
border-radius: 50%;
|
|
31
|
-
box-shadow: 2px 4px 6px
|
|
31
|
+
box-shadow: 2px 4px 6px var(--prp-shadow-medium);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
input.apple-switch:checked {
|
|
35
|
-
box-shadow: inset 20px 0 0 0
|
|
36
|
-
border-color:
|
|
35
|
+
box-shadow: inset 20px 0 0 0 var(--prp-color-success);
|
|
36
|
+
border-color: var(--prp-color-success);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
input.apple-switch:checked:after {
|
|
40
40
|
left: 20px;
|
|
41
|
-
box-shadow: -2px 4px 3px
|
|
41
|
+
box-shadow: -2px 4px 3px var(--prp-shadow-light);
|
|
42
42
|
}
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
align-items: center;
|
|
5
5
|
padding: 1rem;
|
|
6
6
|
margin-bottom: 1.5rem;
|
|
7
|
-
background-color:
|
|
7
|
+
background-color: var(--prp-bg-tertiary);
|
|
8
8
|
border-radius: 8px 0 0 8px;
|
|
9
9
|
|
|
10
10
|
.form-title {
|
|
11
11
|
margin: 0;
|
|
12
12
|
font-size: 1.5rem;
|
|
13
13
|
font-weight: 600;
|
|
14
|
-
color:
|
|
14
|
+
color: var(--prp-text-secondary);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
.form-header-inner {
|
|
@@ -23,50 +23,52 @@
|
|
|
23
23
|
.export-button,
|
|
24
24
|
.import-button {
|
|
25
25
|
padding: 0.5rem 1rem;
|
|
26
|
-
border: 1px solid
|
|
26
|
+
border: 1px solid var(--prp-border-primary);
|
|
27
27
|
border-radius: 4px;
|
|
28
|
-
background-color:
|
|
29
|
-
color:
|
|
28
|
+
background-color: var(--prp-bg-secondary);
|
|
29
|
+
color: var(--prp-text-secondary);
|
|
30
30
|
font-size: 0.875rem;
|
|
31
31
|
cursor: pointer;
|
|
32
32
|
transition: all 0.2s ease;
|
|
33
33
|
|
|
34
34
|
&:hover {
|
|
35
|
-
background-color:
|
|
36
|
-
border-color:
|
|
35
|
+
background-color: var(--prp-bg-tertiary);
|
|
36
|
+
border-color: var(--prp-border-primary);
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
&.dark-theme {
|
|
42
|
-
background-color:
|
|
42
|
+
background-color: var(--prp-bg-primary);
|
|
43
43
|
box-shadow: none;
|
|
44
44
|
|
|
45
45
|
.form-title {
|
|
46
|
-
color:
|
|
46
|
+
color: var(--prp-text-primary);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
.form-actions {
|
|
50
50
|
.export-button {
|
|
51
|
-
background-color:
|
|
52
|
-
border-color:
|
|
53
|
-
color:
|
|
51
|
+
background-color: var(--prp-bg-primary);
|
|
52
|
+
border-color: var(--prp-bg-primary);
|
|
53
|
+
color: var(--prp-text-primary);
|
|
54
54
|
|
|
55
55
|
&:hover {
|
|
56
|
-
background-color:
|
|
57
|
-
border-color:
|
|
56
|
+
background-color: var(--prp-bg-primary);
|
|
57
|
+
border-color: var(--prp-bg-primary);
|
|
58
|
+
opacity: 0.9;
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
.import-button {
|
|
62
63
|
.import-label {
|
|
63
|
-
background-color:
|
|
64
|
-
border-color:
|
|
65
|
-
color:
|
|
64
|
+
background-color: var(--prp-bg-primary);
|
|
65
|
+
border-color: var(--prp-bg-primary);
|
|
66
|
+
color: var(--prp-text-primary);
|
|
66
67
|
|
|
67
68
|
&:hover {
|
|
68
|
-
background-color:
|
|
69
|
-
border-color:
|
|
69
|
+
background-color: var(--prp-bg-primary);
|
|
70
|
+
border-color: var(--prp-bg-primary);
|
|
71
|
+
opacity: 0.9;
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
}
|