Theme Configuration güncellemesi

This commit is contained in:
Sedat Öztürk 2026-03-27 23:17:35 +03:00
parent f57fbda2d6
commit 27ff19ca0d
9 changed files with 219 additions and 91 deletions

View file

@ -5346,6 +5346,12 @@
"en": "Configuration",
"tr": "Yapılandırma"
},
{
"resourceName": "Platform",
"key": "SidePanel.SaveConfig",
"en": "Save Configuration",
"tr": "Yapılandırmayı Kaydet"
},
{
"resourceName": "Platform",
"key": "SidePanel.Mode",

View file

@ -15,7 +15,7 @@
}
.drawer-body {
@apply p-6 h-full overflow-y-auto;
@apply p-4 h-full overflow-y-auto;
}
.drawer-footer {

View file

@ -1,6 +1,7 @@
import classNames from 'classnames'
import Drawer from '@/components/ui/Drawer'
import SidePanelContent, { SidePanelContentProps } from './SidePanelContent'
import CopyButton from '../ThemeConfigurator/CopyButton'
import withHeaderItem from '@/utils/hoc/withHeaderItem'
import { useStoreState, useStoreActions } from '@/store'
import type { CommonProps } from '@/proxy/common'
@ -41,7 +42,12 @@ const _SidePanel = (props: SidePanelProps) => {
</div>
</Tooltip>
<Drawer
title={translate('::SidePanel.Title')}
title={
<div className="flex items-center justify-between gap-x-2">
<h4>{translate('::SidePanel.Title')}</h4>
<CopyButton />
</div>
}
isOpen={panelExpand}
placement={direction === 'rtl' ? 'left' : 'right'}
width={375}

View file

@ -3,9 +3,12 @@ import Button from '@/components/ui/Button'
import toast from '@/components/ui/toast'
import { themeConfig } from '@/proxy/theme/theme.config'
import { useStoreState } from '@/store'
import { FaSave } from 'react-icons/fa'
import { useLocalization } from '@/utils/hooks/useLocalization'
const CopyButton = () => {
const theme = useStoreState((state) => state.theme)
const { translate } = useLocalization()
const handleCopy = () => {
const config = {
@ -31,9 +34,14 @@ const CopyButton = () => {
}
return (
<Button block variant="solid" onClick={handleCopy}>
Copy config
</Button>
<Button
shape="circle"
variant="plain"
size="xs"
onClick={handleCopy}
title={translate('::SidePanel.SaveConfig')}
icon={<FaSave />}
/>
)
}

View file

@ -9,31 +9,42 @@ import { useStoreActions, useStoreState } from '@/store'
import { updateSettingValues } from '@/services/setting-ui.service'
import React from 'react'
const StyleSwitcher = () => {
const StyleSwitcher = ({ onStyleChange }: { onStyleChange?: () => void }) => {
const { translate } = useLocalization()
const { setMode, setStyle, abpConfig } = useStoreActions((actions) => ({
setMode: actions.theme.setMode,
setStyle: actions.theme.setStyle,
abpConfig: actions.abpConfig.getConfig,
}))
const { style } = useStoreState((state) => state.theme)
const { setMode, setStyle, setThemeColor, setThemeColorLevel, abpConfig } = useStoreActions(
(actions) => ({
setMode: actions.theme.setMode,
setStyle: actions.theme.setStyle,
setThemeColor: actions.theme.setThemeColor,
setThemeColorLevel: actions.theme.setThemeColorLevel,
abpConfig: actions.abpConfig.getConfig,
}),
)
const { style, themeColor, primaryColorLevel } = useStoreState((state) => state.theme)
const onSetStyle = React.useCallback(
async (val: string) => {
setStyle(val)
setMode(val.includes('.dark') ? 'dark' : 'light')
async (val: any) => {
setStyle(val.value)
setMode(val.value.includes('.dark') ? 'dark' : 'light')
setThemeColor(val.color?.color)
setThemeColorLevel(val.color?.colorLevel)
//Update setting value
const values: Record<string, string> = {
App_SiteManagement_Theme_Style: val,
App_SiteManagement_Theme_Style: val.value,
}
const resp = await updateSettingValues(values)
if (resp.status !== 204) {
toast.push(<Notification title={resp?.error?.message} type="danger" />, {
placement: 'top-end',
})
}
abpConfig(false)
if (onStyleChange) onStyleChange()
},
[setStyle, setMode, abpConfig, translate],
[setStyle, setMode, abpConfig, translate, onStyleChange],
)
// Custom Option
@ -100,7 +111,7 @@ const StyleSwitcher = () => {
value={styleMapOptions.find((o) => o.value === style)}
options={styleMapOptions}
onChange={(option: any) => {
onSetStyle(option.value)
onSetStyle(option)
}}
components={{
Option: CustomSelectOption,

View file

@ -3,44 +3,30 @@ import LayoutSwitcher from './LayoutSwitcher'
import ThemeSwitcher from './ThemeSwitcher'
import DirectionSwitcher from './DirectionSwitcher'
import NavModeSwitcher from './NavModeSwitcher'
import CopyButton from './CopyButton'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { useStoreState } from '@/store'
import StyleSwitcher from './StyleSwitcher'
import React, { useState, useCallback } from 'react'
import { useLocalization } from '@/utils/hooks/useLocalization'
export type ThemeConfiguratorProps = {
callBackClose?: () => void
}
const ThemeConfigurator = ({ callBackClose }: ThemeConfiguratorProps) => {
const { translate } = useLocalization()
const [modeKey, setModeKey] = useState(0)
const navMode = useStoreState((state) => state.theme.navMode)
// StyleSwitcher'dan tetiklenecek
const handleStyleChange = useCallback(() => {
setModeKey((prev) => prev + 1)
}, [])
return (
<div className="flex flex-col h-full justify-between">
<div className="flex flex-col gap-y-5 mb-6">
<div className="flex items-center justify-between">
<div>
<h6>{translate('::SidePanel.Mode')}</h6>
<span>{translate('::SidePanel.Mode.Description')}</span>
</div>
<ModeSwitcher />
</div>
<div className="flex items-center justify-between">
<div>
<h6>{translate('::SidePanel.Direction')}</h6>
<span>{translate('::SidePanel.Direction.Description')}</span>
</div>
<DirectionSwitcher callBackClose={callBackClose} />
</div>
<div className="flex flex-col gap-y-3 mb-2">
<div>
<h6 className="mb-3">{translate('::App.SiteManagement.Theme.Style')}</h6>
<StyleSwitcher />
</div>
<div>
<h6 className="mb-3">{translate('::SidePanel.Layout')}</h6>
<LayoutSwitcher />
<StyleSwitcher onStyleChange={handleStyleChange} />
</div>
<div>
<h6 className="mb-3">{translate('::SidePanel.NavMode')}</h6>
@ -50,8 +36,25 @@ const ThemeConfigurator = ({ callBackClose }: ThemeConfiguratorProps) => {
<h6 className="mb-3">{translate('::SidePanel.Themed')}</h6>
<ThemeSwitcher />
</div>
<div className="flex items-center justify-between">
<div>
<h6>{translate('::SidePanel.Mode')}</h6>
<span>{translate('::SidePanel.Mode.Description')}</span>
</div>
<ModeSwitcher key={modeKey} />
</div>
<div className="flex items-center justify-between">
<div>
<h6>{translate('::SidePanel.Direction')}</h6>
<span>{translate('::SidePanel.Direction.Description')}</span>
</div>
<DirectionSwitcher callBackClose={callBackClose} />
</div>
<div>
<h6 className="mb-3">{translate('::SidePanel.Layout')}</h6>
<LayoutSwitcher />
</div>
</div>
<CopyButton />
</div>
)
}

View file

@ -28,6 +28,7 @@ const colorList: ColorList[] = [
{ label: 'Green', value: 'green' },
{ label: 'Emerald', value: 'emerald' },
{ label: 'Teal', value: 'teal' },
{ label: 'Gray', value: 'gray' },
{ label: 'Cyan', value: 'cyan' },
{ label: 'Sky', value: 'sky' },
{ label: 'Blue', value: 'blue' },
@ -59,13 +60,13 @@ const ColorBadge = ({ className, themeColor }: { className?: string; themeColor:
const CustomSelectOption = ({ innerProps, label, data, isSelected }: OptionProps<ColorList>) => {
return (
<div
className={`flex items-center justify-between p-2 ${
className={`flex items-center justify-between p-2 cursor-pointer ${
isSelected ? 'bg-gray-100 dark:bg-gray-500' : 'hover:bg-gray-50 dark:hover:bg-gray-600'
}`}
{...innerProps}
>
<div className="flex items-center gap-2">
<ColorBadge themeColor={data.value} />
<ColorBadge themeColor={data.value} className='p-3' />
<span>{label}</span>
</div>
{isSelected && <FaCheck className="text-emerald-500 text-xl" />}
@ -80,7 +81,7 @@ const CustomControl = ({ children, ...props }: ControlProps<ColorList>) => {
return (
<Control {...props}>
{selected && <ColorBadge themeColor={themeColor} className="ltr:ml-4 rtl:mr-4" />}
{selected && <ColorBadge themeColor={themeColor} className="ml-2 p-3" />}
{children}
</Control>
)

View file

@ -2,12 +2,7 @@ import type { Action } from 'easy-peasy'
import { action } from 'easy-peasy'
import { availableNavColorLayouts, themeConfig } from '../proxy/theme/theme.config'
import {
LAYOUT_TYPE_BLANK,
LAYOUT_TYPE_CLASSIC,
LAYOUT_TYPE_DECKED,
LAYOUT_TYPE_MODERN,
LAYOUT_TYPE_SIMPLE,
LAYOUT_TYPE_STACKED_SIDE,
NAV_MODE_TRANSPARENT,
} from '../constants/theme.constant'
import { Direction, Mode, NavMode } from '../proxy/theme/models'

View file

@ -535,51 +535,149 @@ export const widgetIconOptions = iconList.map((icon) => ({
label: icon,
}))
// Tema renk haritası (border ve fill)
const styleColorMap: Record<string, { border: string; fill: string }> = {
'dx.light': { border: '#ffffff', fill: '#337ab7' },
'dx.dark': { border: '#4d4d4d', fill: '#1ca8dd' },
'dx.carmine': { border: '#ffffff', fill: '#f05b41' },
'dx.darkmoon': { border: '#465672', fill: '#3debd3' },
'dx.softblue': { border: '#ffffff', fill: '#7ab8eb' },
'dx.darkviolet': { border: '#17171f', fill: '#9c63ff' },
'dx.greenmist': { border: '#f5f5f5', fill: '#3cbab2' },
'dx.contrast': { border: '#000000', fill: '#ffffff' },
const styleColorMap: Record<
string,
{ border: string; fill: string; color: string; colorLevel: number }
> = {
'dx.light': { border: '#ffffff', fill: '#337ab7', color: 'blue', colorLevel: 600 },
'dx.dark': { border: '#4d4d4d', fill: '#1ca8dd', color: 'sky', colorLevel: 500 },
'dx.carmine': { border: '#ffffff', fill: '#f05b41', color: 'orange', colorLevel: 500 },
'dx.darkmoon': { border: '#465672', fill: '#3debd3', color: 'cyan', colorLevel: 400 },
'dx.softblue': { border: '#ffffff', fill: '#7ab8eb', color: 'sky', colorLevel: 400 },
'dx.darkviolet': { border: '#17171f', fill: '#9c63ff', color: 'violet', colorLevel: 500 },
'dx.greenmist': { border: '#f5f5f5', fill: '#3cbab2', color: 'teal', colorLevel: 500 },
'dx.contrast': { border: '#000000', fill: '#ffffff', color: 'gray', colorLevel: 400 },
// Material
'dx.material.blue.light': { border: '#ffffff', fill: '#03a9f4' },
'dx.material.orange.light': { border: '#ffffff', fill: '#ff5722' },
'dx.material.lime.light': { border: '#ffffff', fill: '#cddc39' },
'dx.material.purple.light': { border: '#ffffff', fill: '#9c27b0' },
'dx.material.teal.light': { border: '#ffffff', fill: '#009688' },
'dx.material.blue.dark': { border: '#363640', fill: '#03a9f4' },
'dx.material.orange.dark': { border: '#363640', fill: '#ff5722' },
'dx.material.lime.dark': { border: '#363640', fill: '#cddc39' },
'dx.material.purple.dark': { border: '#363640', fill: '#9c27b0' },
'dx.material.teal.dark': { border: '#363640', fill: '#009688' },
'dx.material.blue.light': { border: '#ffffff', fill: '#03a9f4', color: 'sky', colorLevel: 500 },
'dx.material.orange.light': {
border: '#ffffff',
fill: '#ff5722',
color: 'orange',
colorLevel: 600,
},
'dx.material.lime.light': { border: '#ffffff', fill: '#cddc39', color: 'lime', colorLevel: 400 },
'dx.material.purple.light': {
border: '#ffffff',
fill: '#9c27b0',
color: 'purple',
colorLevel: 600,
},
'dx.material.teal.light': { border: '#ffffff', fill: '#009688', color: 'teal', colorLevel: 600 },
'dx.material.blue.dark': { border: '#363640', fill: '#03a9f4', color: 'sky', colorLevel: 500 },
'dx.material.orange.dark': {
border: '#363640',
fill: '#ff5722',
color: 'orange',
colorLevel: 600,
},
'dx.material.lime.dark': { border: '#363640', fill: '#cddc39', color: 'lime', colorLevel: 400 },
'dx.material.purple.dark': {
border: '#363640',
fill: '#9c27b0',
color: 'purple',
colorLevel: 600,
},
'dx.material.teal.dark': { border: '#363640', fill: '#009688', color: 'teal', colorLevel: 600 },
// Fluent
'dx.fluent.blue.light': { border: '#ffffff', fill: '#0f6cbd' },
'dx.fluent.saas.light': { border: '#ffffff', fill: '#5486ff' },
'dx.fluent.blue.dark': { border: '#292929', fill: '#479ef5' },
'dx.fluent.saas.dark': { border: '#1e2832', fill: '#5492f6' },
'dx.fluent.blue.light': { border: '#ffffff', fill: '#0f6cbd', color: 'blue', colorLevel: 700 },
'dx.fluent.saas.light': { border: '#ffffff', fill: '#5486ff', color: 'blue', colorLevel: 500 },
'dx.fluent.blue.dark': { border: '#292929', fill: '#479ef5', color: 'blue', colorLevel: 400 },
'dx.fluent.saas.dark': { border: '#1e2832', fill: '#5492f6', color: 'blue', colorLevel: 400 },
'dx.light.compact': { border: '#ffffff', fill: '#337ab7' },
'dx.dark.compact': { border: '#4d4d4d', fill: '#1ca8dd' },
'dx.contrast.compact': { border: '#000000', fill: '#ffffff' },
'dx.material.blue.light.compact': { border: '#ffffff', fill: '#03a9f4' },
'dx.material.orange.light.compact': { border: '#ffffff', fill: '#ff5722' },
'dx.material.lime.light.compact': { border: '#ffffff', fill: '#cddc39' },
'dx.material.purple.light.compact': { border: '#ffffff', fill: '#9c27b0' },
'dx.material.teal.light.compact': { border: '#ffffff', fill: '#009688' },
'dx.material.blue.dark.compact': { border: '#363640', fill: '#03a9f4' },
'dx.material.orange.dark.compact': { border: '#363640', fill: '#ff5722' },
'dx.material.lime.dark.compact': { border: '#363640', fill: '#cddc39' },
'dx.material.purple.dark.compact': { border: '#363640', fill: '#9c27b0' },
'dx.material.teal.dark.compact': { border: '#363640', fill: '#009688' },
'dx.fluent.blue.light.compact': { border: '#ffffff', fill: '#0f6cbd' },
'dx.fluent.saas.light.compact': { border: '#ffffff', fill: '#5486ff' },
'dx.fluent.blue.dark.compact': { border: '#292929', fill: '#479ef5' },
'dx.fluent.saas.dark.compact': { border: '#1e2832', fill: '#5492f6' },
// Compact
'dx.light.compact': { border: '#ffffff', fill: '#337ab7', color: 'blue', colorLevel: 600 },
'dx.dark.compact': { border: '#4d4d4d', fill: '#1ca8dd', color: 'sky', colorLevel: 500 },
'dx.contrast.compact': { border: '#000000', fill: '#ffffff', color: 'gray', colorLevel: 400 },
'dx.material.blue.light.compact': {
border: '#ffffff',
fill: '#03a9f4',
color: 'sky',
colorLevel: 500,
},
'dx.material.orange.light.compact': {
border: '#ffffff',
fill: '#ff5722',
color: 'orange',
colorLevel: 600,
},
'dx.material.lime.light.compact': {
border: '#ffffff',
fill: '#cddc39',
color: 'lime',
colorLevel: 400,
},
'dx.material.purple.light.compact': {
border: '#ffffff',
fill: '#9c27b0',
color: 'purple',
colorLevel: 600,
},
'dx.material.teal.light.compact': {
border: '#ffffff',
fill: '#009688',
color: 'teal',
colorLevel: 600,
},
'dx.material.blue.dark.compact': {
border: '#363640',
fill: '#03a9f4',
color: 'sky',
colorLevel: 500,
},
'dx.material.orange.dark.compact': {
border: '#363640',
fill: '#ff5722',
color: 'orange',
colorLevel: 600,
},
'dx.material.lime.dark.compact': {
border: '#363640',
fill: '#cddc39',
color: 'lime',
colorLevel: 400,
},
'dx.material.purple.dark.compact': {
border: '#363640',
fill: '#9c27b0',
color: 'purple',
colorLevel: 600,
},
'dx.material.teal.dark.compact': {
border: '#363640',
fill: '#009688',
color: 'teal',
colorLevel: 600,
},
'dx.fluent.blue.light.compact': {
border: '#ffffff',
fill: '#0f6cbd',
color: 'blue',
colorLevel: 700,
},
'dx.fluent.saas.light.compact': {
border: '#ffffff',
fill: '#5486ff',
color: 'blue',
colorLevel: 500,
},
'dx.fluent.blue.dark.compact': {
border: '#292929',
fill: '#479ef5',
color: 'blue',
colorLevel: 400,
},
'dx.fluent.saas.dark.compact': {
border: '#1e2832',
fill: '#5492f6',
color: 'blue',
colorLevel: 400,
},
}
export const styleMapOptions = enumToList<string>(StyleEnum).map((opt) => ({