Fix Khanegi custom theme live preview for primary and scheme colors.

Edit the scheme matching the preview dark/light toggle and surface primary in the sample card so custom changes are visible before save.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-05 13:34:22 +03:30
co-authored by Cursor
parent f41424ef0d
commit 7889a7905f
+62 -22
View File
@@ -40,13 +40,17 @@ const config = reactive(createDefaultKhanegiUiConfig()) as ReturnType<
const hasDefaultSnapshot = computed(() => Boolean(config.defaultSnapshot)) const hasDefaultSnapshot = computed(() => Boolean(config.defaultSnapshot))
const isCustom = computed(() => selectedPreset.value === 'custom') const isCustom = computed(() => selectedPreset.value === 'custom')
/** رنگ‌های همان حالتی که در پیش‌نمایش دیده می‌شود (dark/light) */
const activePreviewColors = computed(() => { const activePreviewColors = computed(() => {
const schemes = config.app.theme.schemes const schemes = config.app.theme.schemes
if (schemes?.[previewMode.value]) return schemes[previewMode.value] if (schemes?.[previewMode.value]) return schemes[previewMode.value]
return config.app.theme.colors return config.app.theme.colors
}) })
const colorKeys = computed(() => Object.keys(config.app.theme.colors)) /** فیلدهای رنگ سفارشی روی همان scheme پیش‌نمایش ویرایش می‌شوند تا نمونه زنده بماند */
const editableColors = computed(() => activePreviewColors.value)
const colorKeys = computed(() => Object.keys(editableColors.value))
const previewStyle = computed(() => ({ const previewStyle = computed(() => ({
background: activePreviewColors.value.pageBg, background: activePreviewColors.value.pageBg,
@@ -54,6 +58,12 @@ const previewStyle = computed(() => ({
borderRadius: `${config.app.theme.borderRadius.lg}px`, borderRadius: `${config.app.theme.borderRadius.lg}px`,
})) }))
const primaryStyle = computed(() => ({
background: activePreviewColors.value.primary,
color: activePreviewColors.value.onAccent,
borderRadius: `${config.app.theme.borderRadius.md}px`,
}))
const accentStyle = computed(() => ({ const accentStyle = computed(() => ({
background: activePreviewColors.value.secondary, background: activePreviewColors.value.secondary,
color: activePreviewColors.value.onAccent, color: activePreviewColors.value.onAccent,
@@ -64,13 +74,14 @@ function selectPreset(id: KhanegiPresetId) {
selectedPreset.value = id selectedPreset.value = id
if (id === 'custom') { if (id === 'custom') {
config.app.theme.presetId = 'custom' config.app.theme.presetId = 'custom'
if (!config.app.theme.schemes) { ensureSchemes()
config.app.theme.schemes = { // clone so custom edits never mutate shared preset objects
dark: { ...config.app.theme.colors }, config.app.theme.schemes = {
light: { ...config.app.theme.colors }, dark: { ...config.app.theme.schemes!.dark },
} light: { ...config.app.theme.schemes!.light },
} }
message.info('حالت سفارشی — می‌توانید جزئیات را ویرایش کنید') syncActiveColorsFromMode(config)
message.info('حالت سفارشی — تغییرات فوراً در نمونه کارت دیده می‌شود')
return return
} }
applyKhanegiPreset(config, id) applyKhanegiPreset(config, id)
@@ -82,14 +93,18 @@ function selectPreset(id: KhanegiPresetId) {
function onModeChange() { function onModeChange() {
syncActiveColorsFromMode(config) syncActiveColorsFromMode(config)
previewMode.value = config.app.theme.mode === 'light' ? 'light' : 'dark' previewMode.value = config.app.theme.mode === 'light' ? 'light' : 'dark'
if (isCustom.value) markCustomAndSyncSchemes(config) if (isCustom.value) {
config.app.theme.presetId = 'custom'
}
} }
/** بعد از هر تغییر رنگ/گوشه: سفارشی شو و colors فعال را با scheme حالت اپ هم‌تراز کن */
function onColorEdit() { function onColorEdit() {
if (!isCustom.value) { selectedPreset.value = 'custom'
selectedPreset.value = 'custom' config.app.theme.presetId = 'custom'
} ensureSchemes()
markCustomAndSyncSchemes(config) const appMode = config.app.theme.mode === 'light' ? 'light' : 'dark'
Object.assign(config.app.theme.colors, config.app.theme.schemes![appMode])
} }
function syncAdvancedJson() { function syncAdvancedJson() {
@@ -310,14 +325,18 @@ onMounted(load)
<button type="button" :class="{ on: previewMode === 'dark' }" @click="previewMode = 'dark'">تیره</button> <button type="button" :class="{ on: previewMode === 'dark' }" @click="previewMode = 'dark'">تیره</button>
<button type="button" :class="{ on: previewMode === 'light' }" @click="previewMode = 'light'">روشن</button> <button type="button" :class="{ on: previewMode === 'light' }" @click="previewMode = 'light'">روشن</button>
</div> </div>
<span class="preview-chip" :style="accentStyle">اکسنت</span>
</div> </div>
<div
class="preview-primary-strip"
:style="{ background: activePreviewColors.primary, borderRadius: config.app.theme.borderRadius.sm + 'px' }"
/>
<div <div
class="preview-card" class="preview-card"
:style="{ :style="{
background: activePreviewColors.surface, background: activePreviewColors.surface,
borderRadius: config.app.theme.borderRadius.md + 'px', borderRadius: config.app.theme.borderRadius.md + 'px',
color: activePreviewColors.textPrimary, color: activePreviewColors.textPrimary,
border: `1px solid ${activePreviewColors.border}`,
}" }"
> >
<div style="opacity: 0.7; font-size: 12px">surface</div> <div style="opacity: 0.7; font-size: 12px">surface</div>
@@ -325,6 +344,10 @@ onMounted(load)
<div style="margin-top: 6px; font-size: 12px" :style="{ color: activePreviewColors.textSecondary }"> <div style="margin-top: 6px; font-size: 12px" :style="{ color: activePreviewColors.textSecondary }">
متن فرعی با رنگ theme متن فرعی با رنگ theme
</div> </div>
<div class="preview-actions">
<span class="preview-chip" :style="primaryStyle">اصلی</span>
<span class="preview-chip" :style="accentStyle">اکسنت</span>
</div>
</div> </div>
</div> </div>
@@ -348,27 +371,30 @@ onMounted(load)
<section class="form-section"> <section class="form-section">
<h3>رنگ‌ها {{ isCustom ? '(سفارشی)' : '(فقط‌خواندنی تا انتخاب سفارشی)' }}</h3> <h3>رنگ‌ها {{ isCustom ? '(سفارشی)' : '(فقط‌خواندنی تا انتخاب سفارشی)' }}</h3>
<p v-if="isCustom" class="page-sub" style="margin-bottom: 12px">
ویرایش برای حالت «{{ previewMode === 'dark' ? 'تیره' : 'روشن' }}» پیش‌نمایش است؛ با سوییچ تیره/روشن بالای کارت، هر پالت جداگانه تنظیم می‌شود.
</p>
<div class="color-grid"> <div class="color-grid">
<label v-for="key in colorKeys" :key="key" class="color-field"> <label v-for="key in colorKeys" :key="`${previewMode}-${key}`" class="color-field">
<span>{{ colorFieldLabels[key] || key }}</span> <span>{{ colorFieldLabels[key] || key }} · {{ previewMode === 'dark' ? 'تیره' : 'روشن' }}</span>
<div class="color-row"> <div class="color-row">
<span <span
class="color-swatch" class="color-swatch"
:style="{ background: (config.app.theme.colors as any)[key] }" :style="{ background: (editableColors as any)[key] }"
/> />
<input <input
v-if="String((config.app.theme.colors as any)[key] || '').startsWith('#')" v-if="String((editableColors as any)[key] || '').startsWith('#')"
v-model="(config.app.theme.colors as any)[key]" v-model="(editableColors as any)[key]"
type="color" type="color"
class="color-native" class="color-native"
:disabled="!isCustom" :disabled="!isCustom"
@change="onColorEdit" @input="onColorEdit"
/> />
<a-input <a-input
v-model:value="(config.app.theme.colors as any)[key]" v-model:value="(editableColors as any)[key]"
dir="ltr" dir="ltr"
:disabled="!isCustom" :disabled="!isCustom"
@change="onColorEdit" @update:value="onColorEdit"
/> />
</div> </div>
</label> </label>
@@ -581,6 +607,11 @@ onMounted(load)
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04); box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04);
} }
.preview-primary-strip {
height: 8px;
margin-bottom: 12px;
}
.preview-bar { .preview-bar {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -617,8 +648,17 @@ onMounted(load)
font-weight: 700; font-weight: 700;
} }
.preview-actions {
display: flex;
gap: 8px;
margin-top: 14px;
flex-wrap: wrap;
}
.preview-chip { .preview-chip {
padding: 4px 10px; display: inline-flex;
align-items: center;
padding: 6px 12px;
font-size: 12px; font-size: 12px;
font-weight: 700; font-weight: 700;
} }