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", "en": "Configuration",
"tr": "Yapılandırma" "tr": "Yapılandırma"
}, },
{
"resourceName": "Platform",
"key": "SidePanel.SaveConfig",
"en": "Save Configuration",
"tr": "Yapılandırmayı Kaydet"
},
{ {
"resourceName": "Platform", "resourceName": "Platform",
"key": "SidePanel.Mode", "key": "SidePanel.Mode",

View file

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

View file

@ -1,6 +1,7 @@
import classNames from 'classnames' import classNames from 'classnames'
import Drawer from '@/components/ui/Drawer' import Drawer from '@/components/ui/Drawer'
import SidePanelContent, { SidePanelContentProps } from './SidePanelContent' import SidePanelContent, { SidePanelContentProps } from './SidePanelContent'
import CopyButton from '../ThemeConfigurator/CopyButton'
import withHeaderItem from '@/utils/hoc/withHeaderItem' import withHeaderItem from '@/utils/hoc/withHeaderItem'
import { useStoreState, useStoreActions } from '@/store' import { useStoreState, useStoreActions } from '@/store'
import type { CommonProps } from '@/proxy/common' import type { CommonProps } from '@/proxy/common'
@ -41,7 +42,12 @@ const _SidePanel = (props: SidePanelProps) => {
</div> </div>
</Tooltip> </Tooltip>
<Drawer <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} isOpen={panelExpand}
placement={direction === 'rtl' ? 'left' : 'right'} placement={direction === 'rtl' ? 'left' : 'right'}
width={375} width={375}

View file

@ -3,9 +3,12 @@ import Button from '@/components/ui/Button'
import toast from '@/components/ui/toast' import toast from '@/components/ui/toast'
import { themeConfig } from '@/proxy/theme/theme.config' import { themeConfig } from '@/proxy/theme/theme.config'
import { useStoreState } from '@/store' import { useStoreState } from '@/store'
import { FaSave } from 'react-icons/fa'
import { useLocalization } from '@/utils/hooks/useLocalization'
const CopyButton = () => { const CopyButton = () => {
const theme = useStoreState((state) => state.theme) const theme = useStoreState((state) => state.theme)
const { translate } = useLocalization()
const handleCopy = () => { const handleCopy = () => {
const config = { const config = {
@ -31,9 +34,14 @@ const CopyButton = () => {
} }
return ( return (
<Button block variant="solid" onClick={handleCopy}> <Button
Copy config shape="circle"
</Button> 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 { updateSettingValues } from '@/services/setting-ui.service'
import React from 'react' import React from 'react'
const StyleSwitcher = () => { const StyleSwitcher = ({ onStyleChange }: { onStyleChange?: () => void }) => {
const { translate } = useLocalization() const { translate } = useLocalization()
const { setMode, setStyle, abpConfig } = useStoreActions((actions) => ({ const { setMode, setStyle, setThemeColor, setThemeColorLevel, abpConfig } = useStoreActions(
setMode: actions.theme.setMode, (actions) => ({
setStyle: actions.theme.setStyle, setMode: actions.theme.setMode,
abpConfig: actions.abpConfig.getConfig, setStyle: actions.theme.setStyle,
})) setThemeColor: actions.theme.setThemeColor,
const { style } = useStoreState((state) => state.theme) setThemeColorLevel: actions.theme.setThemeColorLevel,
abpConfig: actions.abpConfig.getConfig,
}),
)
const { style, themeColor, primaryColorLevel } = useStoreState((state) => state.theme)
const onSetStyle = React.useCallback( const onSetStyle = React.useCallback(
async (val: string) => { async (val: any) => {
setStyle(val) setStyle(val.value)
setMode(val.includes('.dark') ? 'dark' : 'light') setMode(val.value.includes('.dark') ? 'dark' : 'light')
setThemeColor(val.color?.color)
setThemeColorLevel(val.color?.colorLevel)
//Update setting value
const values: Record<string, string> = { const values: Record<string, string> = {
App_SiteManagement_Theme_Style: val, App_SiteManagement_Theme_Style: val.value,
} }
const resp = await updateSettingValues(values) const resp = await updateSettingValues(values)
if (resp.status !== 204) { if (resp.status !== 204) {
toast.push(<Notification title={resp?.error?.message} type="danger" />, { toast.push(<Notification title={resp?.error?.message} type="danger" />, {
placement: 'top-end', placement: 'top-end',
}) })
} }
abpConfig(false) abpConfig(false)
if (onStyleChange) onStyleChange()
}, },
[setStyle, setMode, abpConfig, translate], [setStyle, setMode, abpConfig, translate, onStyleChange],
) )
// Custom Option // Custom Option
@ -100,7 +111,7 @@ const StyleSwitcher = () => {
value={styleMapOptions.find((o) => o.value === style)} value={styleMapOptions.find((o) => o.value === style)}
options={styleMapOptions} options={styleMapOptions}
onChange={(option: any) => { onChange={(option: any) => {
onSetStyle(option.value) onSetStyle(option)
}} }}
components={{ components={{
Option: CustomSelectOption, Option: CustomSelectOption,

View file

@ -3,44 +3,30 @@ import LayoutSwitcher from './LayoutSwitcher'
import ThemeSwitcher from './ThemeSwitcher' import ThemeSwitcher from './ThemeSwitcher'
import DirectionSwitcher from './DirectionSwitcher' import DirectionSwitcher from './DirectionSwitcher'
import NavModeSwitcher from './NavModeSwitcher' import NavModeSwitcher from './NavModeSwitcher'
import CopyButton from './CopyButton'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { useStoreState } from '@/store'
import StyleSwitcher from './StyleSwitcher' import StyleSwitcher from './StyleSwitcher'
import React, { useState, useCallback } from 'react'
import { useLocalization } from '@/utils/hooks/useLocalization'
export type ThemeConfiguratorProps = { export type ThemeConfiguratorProps = {
callBackClose?: () => void callBackClose?: () => void
} }
const ThemeConfigurator = ({ callBackClose }: ThemeConfiguratorProps) => { const ThemeConfigurator = ({ callBackClose }: ThemeConfiguratorProps) => {
const { translate } = useLocalization() 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 ( return (
<div className="flex flex-col h-full justify-between"> <div className="flex flex-col h-full justify-between">
<div className="flex flex-col gap-y-5 mb-6"> <div className="flex flex-col gap-y-3 mb-2">
<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> <div>
<h6 className="mb-3">{translate('::App.SiteManagement.Theme.Style')}</h6> <h6 className="mb-3">{translate('::App.SiteManagement.Theme.Style')}</h6>
<StyleSwitcher /> <StyleSwitcher onStyleChange={handleStyleChange} />
</div>
<div>
<h6 className="mb-3">{translate('::SidePanel.Layout')}</h6>
<LayoutSwitcher />
</div> </div>
<div> <div>
<h6 className="mb-3">{translate('::SidePanel.NavMode')}</h6> <h6 className="mb-3">{translate('::SidePanel.NavMode')}</h6>
@ -50,8 +36,25 @@ const ThemeConfigurator = ({ callBackClose }: ThemeConfiguratorProps) => {
<h6 className="mb-3">{translate('::SidePanel.Themed')}</h6> <h6 className="mb-3">{translate('::SidePanel.Themed')}</h6>
<ThemeSwitcher /> <ThemeSwitcher />
</div> </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> </div>
<CopyButton />
</div> </div>
) )
} }

View file

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

View file

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

View file

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