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:
+62
-22
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user