feat(hamkar-ui): add menu icon and active icon color pickers
Extend Hamkar appearance with separate default/selected icon colors and show them in the mobile preview. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,6 +9,10 @@ export type HamkarBrandColors = {
|
||||
secondaryColor: string
|
||||
/** Registry `background_color` → CSS `--bg` (dynamicMode) */
|
||||
backgroundColor: string
|
||||
/** Menu / header icon stroke (brand_chrome) */
|
||||
menuIconColor: string
|
||||
/** Selected menu icon + label (brand_chrome) */
|
||||
menuIconActiveColor: string
|
||||
}
|
||||
|
||||
export type HamkarUiSettings = {
|
||||
@@ -102,6 +106,8 @@ function normalizeBrand(raw: unknown): HamkarBrandColors {
|
||||
primaryColor: asColor(brand.primaryColor),
|
||||
secondaryColor: asColor(brand.secondaryColor),
|
||||
backgroundColor: asColor(brand.backgroundColor),
|
||||
menuIconColor: asColor(brand.menuIconColor),
|
||||
menuIconActiveColor: asColor(brand.menuIconActiveColor),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +121,8 @@ export function createDefaultHamkarUiConfig(): HamkarUiConfig {
|
||||
primaryColor: '',
|
||||
secondaryColor: '',
|
||||
backgroundColor: '',
|
||||
menuIconColor: '',
|
||||
menuIconActiveColor: '',
|
||||
},
|
||||
settings: {
|
||||
pricing_box_theme: '1',
|
||||
@@ -177,6 +185,10 @@ export function hamkarUiToApiPayload(config: HamkarUiConfig) {
|
||||
primaryColor: emptyToNull(normalized.defaultSnapshot.brand.primaryColor),
|
||||
secondaryColor: emptyToNull(normalized.defaultSnapshot.brand.secondaryColor),
|
||||
backgroundColor: emptyToNull(normalized.defaultSnapshot.brand.backgroundColor),
|
||||
menuIconColor: emptyToNull(normalized.defaultSnapshot.brand.menuIconColor),
|
||||
menuIconActiveColor: emptyToNull(
|
||||
normalized.defaultSnapshot.brand.menuIconActiveColor,
|
||||
),
|
||||
},
|
||||
settings: { ...normalized.defaultSnapshot.settings },
|
||||
}
|
||||
@@ -191,6 +203,8 @@ export function hamkarUiToApiPayload(config: HamkarUiConfig) {
|
||||
primaryColor: emptyToNull(normalized.brand.primaryColor),
|
||||
secondaryColor: emptyToNull(normalized.brand.secondaryColor),
|
||||
backgroundColor: emptyToNull(normalized.brand.backgroundColor),
|
||||
menuIconColor: emptyToNull(normalized.brand.menuIconColor),
|
||||
menuIconActiveColor: emptyToNull(normalized.brand.menuIconActiveColor),
|
||||
},
|
||||
settings: { ...normalized.settings },
|
||||
...(snap ? { defaultSnapshot: snap } : {}),
|
||||
|
||||
@@ -75,6 +75,8 @@ const palette = computed(() => ({
|
||||
header: swatchHex(config.brand.primaryColor, '#ffffff'),
|
||||
accent: swatchHex(config.brand.secondaryColor, '#2563EB'),
|
||||
page: swatchHex(config.brand.backgroundColor, '#fafafa'),
|
||||
menuIcon: swatchHex(config.brand.menuIconColor, textOn(swatchHex(config.brand.primaryColor, '#ffffff'))),
|
||||
menuIconActive: swatchHex(config.brand.menuIconActiveColor, '#2563EB'),
|
||||
}))
|
||||
|
||||
const previewChrome = computed(() => {
|
||||
@@ -99,8 +101,14 @@ const previewChrome = computed(() => {
|
||||
color: onHeader,
|
||||
borderTop: headerEdge,
|
||||
},
|
||||
brandFooter: {
|
||||
background: p.header,
|
||||
color: p.menuIcon,
|
||||
},
|
||||
accent: p.accent,
|
||||
muted: onHeader === '#ffffff' ? 'rgba(255,255,255,0.72)' : '#616161',
|
||||
menuIcon: p.menuIcon,
|
||||
menuIconActive: p.menuIconActive,
|
||||
muted: p.menuIcon,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -120,6 +128,14 @@ function seedEditableColors(normalized: HamkarUiConfig) {
|
||||
if (!normalized.brand.secondaryColor) normalized.brand.secondaryColor = effective.accent
|
||||
if (!normalized.brand.backgroundColor) normalized.brand.backgroundColor = effective.pageBg
|
||||
}
|
||||
if (!normalized.brand.menuIconColor) {
|
||||
normalized.brand.menuIconColor = textOn(
|
||||
swatchHex(normalized.brand.primaryColor, effective.headerBg),
|
||||
)
|
||||
}
|
||||
if (!normalized.brand.menuIconActiveColor) {
|
||||
normalized.brand.menuIconActiveColor = effective.accent
|
||||
}
|
||||
}
|
||||
|
||||
function ensureDefaultSnapshot(
|
||||
@@ -148,6 +164,8 @@ function ensureDefaultSnapshot(
|
||||
primaryColor: String(opts.primaryColor || normalized.brand.primaryColor || ''),
|
||||
secondaryColor: String(opts.secondaryColor || normalized.brand.secondaryColor || ''),
|
||||
backgroundColor: String(opts.backgroundColor || normalized.brand.backgroundColor || ''),
|
||||
menuIconColor: String(normalized.brand.menuIconColor || ''),
|
||||
menuIconActiveColor: String(normalized.brand.menuIconActiveColor || ''),
|
||||
},
|
||||
settings: { ...normalized.settings },
|
||||
})
|
||||
@@ -267,6 +285,8 @@ function resetToDefault() {
|
||||
config.brand.primaryColor = snap.brand.primaryColor
|
||||
config.brand.secondaryColor = snap.brand.secondaryColor
|
||||
config.brand.backgroundColor = snap.brand.backgroundColor
|
||||
config.brand.menuIconColor = snap.brand.menuIconColor
|
||||
config.brand.menuIconActiveColor = snap.brand.menuIconActiveColor
|
||||
config.settings = { ...snap.settings }
|
||||
// Named factory modes must not keep brand chrome overrides.
|
||||
config.brandManaged = snap.registryMode === 'dynamicMode'
|
||||
@@ -328,15 +348,20 @@ onMounted(load)
|
||||
v-for="(item, i) in NAV_ITEMS"
|
||||
:key="item"
|
||||
class="app-preview-nav"
|
||||
:style="{ color: i === 0 ? previewChrome.accent : previewChrome.muted }"
|
||||
:style="{ color: i === 0 ? previewChrome.menuIconActive : previewChrome.menuIcon }"
|
||||
>
|
||||
<span
|
||||
class="app-preview-dot"
|
||||
:style="{ background: i === 0 ? previewChrome.accent : previewChrome.muted }"
|
||||
:style="{
|
||||
background: i === 0 ? previewChrome.menuIconActive : previewChrome.menuIcon,
|
||||
}"
|
||||
/>
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-preview-brand" :style="previewChrome.brandFooter">
|
||||
توسعه و طراحی توسط
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -399,6 +424,40 @@ onMounted(load)
|
||||
class="palette-hex"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="palette-card">
|
||||
<span class="palette-title">آیکون منو</span>
|
||||
<span class="palette-swatch" :style="{ background: palette.menuIcon }">
|
||||
<input
|
||||
:value="palette.menuIcon"
|
||||
type="color"
|
||||
@input="config.brand.menuIconColor = ($event.target as HTMLInputElement).value"
|
||||
/>
|
||||
</span>
|
||||
<a-input
|
||||
v-model:value="config.brand.menuIconColor"
|
||||
dir="ltr"
|
||||
size="large"
|
||||
class="palette-hex"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="palette-card">
|
||||
<span class="palette-title">آیکون فعال</span>
|
||||
<span class="palette-swatch" :style="{ background: palette.menuIconActive }">
|
||||
<input
|
||||
:value="palette.menuIconActive"
|
||||
type="color"
|
||||
@input="config.brand.menuIconActiveColor = ($event.target as HTMLInputElement).value"
|
||||
/>
|
||||
</span>
|
||||
<a-input
|
||||
v-model:value="config.brand.menuIconActiveColor"
|
||||
dir="ltr"
|
||||
size="large"
|
||||
class="palette-hex"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -582,6 +641,13 @@ onMounted(load)
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.app-preview-brand {
|
||||
padding: 6px 10px;
|
||||
text-align: center;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.palette-grid {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
Reference in New Issue
Block a user