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
|
secondaryColor: string
|
||||||
/** Registry `background_color` → CSS `--bg` (dynamicMode) */
|
/** Registry `background_color` → CSS `--bg` (dynamicMode) */
|
||||||
backgroundColor: string
|
backgroundColor: string
|
||||||
|
/** Menu / header icon stroke (brand_chrome) */
|
||||||
|
menuIconColor: string
|
||||||
|
/** Selected menu icon + label (brand_chrome) */
|
||||||
|
menuIconActiveColor: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HamkarUiSettings = {
|
export type HamkarUiSettings = {
|
||||||
@@ -102,6 +106,8 @@ function normalizeBrand(raw: unknown): HamkarBrandColors {
|
|||||||
primaryColor: asColor(brand.primaryColor),
|
primaryColor: asColor(brand.primaryColor),
|
||||||
secondaryColor: asColor(brand.secondaryColor),
|
secondaryColor: asColor(brand.secondaryColor),
|
||||||
backgroundColor: asColor(brand.backgroundColor),
|
backgroundColor: asColor(brand.backgroundColor),
|
||||||
|
menuIconColor: asColor(brand.menuIconColor),
|
||||||
|
menuIconActiveColor: asColor(brand.menuIconActiveColor),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +121,8 @@ export function createDefaultHamkarUiConfig(): HamkarUiConfig {
|
|||||||
primaryColor: '',
|
primaryColor: '',
|
||||||
secondaryColor: '',
|
secondaryColor: '',
|
||||||
backgroundColor: '',
|
backgroundColor: '',
|
||||||
|
menuIconColor: '',
|
||||||
|
menuIconActiveColor: '',
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
pricing_box_theme: '1',
|
pricing_box_theme: '1',
|
||||||
@@ -177,6 +185,10 @@ export function hamkarUiToApiPayload(config: HamkarUiConfig) {
|
|||||||
primaryColor: emptyToNull(normalized.defaultSnapshot.brand.primaryColor),
|
primaryColor: emptyToNull(normalized.defaultSnapshot.brand.primaryColor),
|
||||||
secondaryColor: emptyToNull(normalized.defaultSnapshot.brand.secondaryColor),
|
secondaryColor: emptyToNull(normalized.defaultSnapshot.brand.secondaryColor),
|
||||||
backgroundColor: emptyToNull(normalized.defaultSnapshot.brand.backgroundColor),
|
backgroundColor: emptyToNull(normalized.defaultSnapshot.brand.backgroundColor),
|
||||||
|
menuIconColor: emptyToNull(normalized.defaultSnapshot.brand.menuIconColor),
|
||||||
|
menuIconActiveColor: emptyToNull(
|
||||||
|
normalized.defaultSnapshot.brand.menuIconActiveColor,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
settings: { ...normalized.defaultSnapshot.settings },
|
settings: { ...normalized.defaultSnapshot.settings },
|
||||||
}
|
}
|
||||||
@@ -191,6 +203,8 @@ export function hamkarUiToApiPayload(config: HamkarUiConfig) {
|
|||||||
primaryColor: emptyToNull(normalized.brand.primaryColor),
|
primaryColor: emptyToNull(normalized.brand.primaryColor),
|
||||||
secondaryColor: emptyToNull(normalized.brand.secondaryColor),
|
secondaryColor: emptyToNull(normalized.brand.secondaryColor),
|
||||||
backgroundColor: emptyToNull(normalized.brand.backgroundColor),
|
backgroundColor: emptyToNull(normalized.brand.backgroundColor),
|
||||||
|
menuIconColor: emptyToNull(normalized.brand.menuIconColor),
|
||||||
|
menuIconActiveColor: emptyToNull(normalized.brand.menuIconActiveColor),
|
||||||
},
|
},
|
||||||
settings: { ...normalized.settings },
|
settings: { ...normalized.settings },
|
||||||
...(snap ? { defaultSnapshot: snap } : {}),
|
...(snap ? { defaultSnapshot: snap } : {}),
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ const palette = computed(() => ({
|
|||||||
header: swatchHex(config.brand.primaryColor, '#ffffff'),
|
header: swatchHex(config.brand.primaryColor, '#ffffff'),
|
||||||
accent: swatchHex(config.brand.secondaryColor, '#2563EB'),
|
accent: swatchHex(config.brand.secondaryColor, '#2563EB'),
|
||||||
page: swatchHex(config.brand.backgroundColor, '#fafafa'),
|
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(() => {
|
const previewChrome = computed(() => {
|
||||||
@@ -99,8 +101,14 @@ const previewChrome = computed(() => {
|
|||||||
color: onHeader,
|
color: onHeader,
|
||||||
borderTop: headerEdge,
|
borderTop: headerEdge,
|
||||||
},
|
},
|
||||||
|
brandFooter: {
|
||||||
|
background: p.header,
|
||||||
|
color: p.menuIcon,
|
||||||
|
},
|
||||||
accent: p.accent,
|
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.secondaryColor) normalized.brand.secondaryColor = effective.accent
|
||||||
if (!normalized.brand.backgroundColor) normalized.brand.backgroundColor = effective.pageBg
|
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(
|
function ensureDefaultSnapshot(
|
||||||
@@ -148,6 +164,8 @@ function ensureDefaultSnapshot(
|
|||||||
primaryColor: String(opts.primaryColor || normalized.brand.primaryColor || ''),
|
primaryColor: String(opts.primaryColor || normalized.brand.primaryColor || ''),
|
||||||
secondaryColor: String(opts.secondaryColor || normalized.brand.secondaryColor || ''),
|
secondaryColor: String(opts.secondaryColor || normalized.brand.secondaryColor || ''),
|
||||||
backgroundColor: String(opts.backgroundColor || normalized.brand.backgroundColor || ''),
|
backgroundColor: String(opts.backgroundColor || normalized.brand.backgroundColor || ''),
|
||||||
|
menuIconColor: String(normalized.brand.menuIconColor || ''),
|
||||||
|
menuIconActiveColor: String(normalized.brand.menuIconActiveColor || ''),
|
||||||
},
|
},
|
||||||
settings: { ...normalized.settings },
|
settings: { ...normalized.settings },
|
||||||
})
|
})
|
||||||
@@ -267,6 +285,8 @@ function resetToDefault() {
|
|||||||
config.brand.primaryColor = snap.brand.primaryColor
|
config.brand.primaryColor = snap.brand.primaryColor
|
||||||
config.brand.secondaryColor = snap.brand.secondaryColor
|
config.brand.secondaryColor = snap.brand.secondaryColor
|
||||||
config.brand.backgroundColor = snap.brand.backgroundColor
|
config.brand.backgroundColor = snap.brand.backgroundColor
|
||||||
|
config.brand.menuIconColor = snap.brand.menuIconColor
|
||||||
|
config.brand.menuIconActiveColor = snap.brand.menuIconActiveColor
|
||||||
config.settings = { ...snap.settings }
|
config.settings = { ...snap.settings }
|
||||||
// Named factory modes must not keep brand chrome overrides.
|
// Named factory modes must not keep brand chrome overrides.
|
||||||
config.brandManaged = snap.registryMode === 'dynamicMode'
|
config.brandManaged = snap.registryMode === 'dynamicMode'
|
||||||
@@ -328,15 +348,20 @@ onMounted(load)
|
|||||||
v-for="(item, i) in NAV_ITEMS"
|
v-for="(item, i) in NAV_ITEMS"
|
||||||
:key="item"
|
:key="item"
|
||||||
class="app-preview-nav"
|
class="app-preview-nav"
|
||||||
:style="{ color: i === 0 ? previewChrome.accent : previewChrome.muted }"
|
:style="{ color: i === 0 ? previewChrome.menuIconActive : previewChrome.menuIcon }"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="app-preview-dot"
|
class="app-preview-dot"
|
||||||
:style="{ background: i === 0 ? previewChrome.accent : previewChrome.muted }"
|
:style="{
|
||||||
|
background: i === 0 ? previewChrome.menuIconActive : previewChrome.menuIcon,
|
||||||
|
}"
|
||||||
/>
|
/>
|
||||||
{{ item }}
|
{{ item }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="app-preview-brand" :style="previewChrome.brandFooter">
|
||||||
|
توسعه و طراحی توسط
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -399,6 +424,40 @@ onMounted(load)
|
|||||||
class="palette-hex"
|
class="palette-hex"
|
||||||
/>
|
/>
|
||||||
</label>
|
</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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -582,6 +641,13 @@ onMounted(load)
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-preview-brand {
|
||||||
|
padding: 6px 10px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.palette-grid {
|
.palette-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|||||||
Reference in New Issue
Block a user